{"name":"io.github.oscarleoo/chartlink","slug":"oscarleoo-chartlink","title":"chartlink","description":"Charts and tables for AI agents with live-updating embed links. No account: signup returns a key.","url":"https://mcp.market/server/oscarleoo-chartlink","rating":null,"grade":"A","score":91,"certified":false,"status":"active","category":"other","tags":[],"presence":{"score":23,"stars":0,"forks":0,"downloads_week":null,"last_push_at":"2026-09-18T07:00:47.000Z","license":"MIT"},"claimed":false,"transport":"mixed","callable_via_gateway":true,"default_price_micros":0,"repository":"https://github.com/oscarleoo/chartlink-agents","website":"https://chartlink.app","version":"0.1.2","remotes":[{"type":"streamable-http","url":"https://chartlink.app/mcp","headers":[{"description":"Bearer <chartlink API key>. Get one with POST https://chartlink.app/api/signup (no account needed; the key is shown once).","isRequired":true,"isSecret":true,"name":"Authorization"}]}],"packages":[{"registryType":"npm","identifier":"chartlink-mcp","version":"0.1.2","transport":{"type":"stdio"},"environmentVariables":[{"description":"chartlink API key (viz_...). POST https://chartlink.app/api/signup returns one, no account needed. Optional for listing tools; required for calling them.","isSecret":true,"name":"CHARTLINK_API_KEY"}]}],"tools":[{"name":"clear_data_source","description":"Stops scheduled fetches; the chart keeps its current data.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"create_asset","description":"Creates a draft. The response includes a low-res PNG preview INLINE — look at it, then iterate with update_asset. Nothing is public until publish_asset, or until you pass publish: true here (one call instead of two, once the chart is final). A success means the config validated AND rendered. Style comes from the defaults and your brand, if any — only set config fields the story needs. data = {columns: [{id,type,...}], rows: [[...], ...]} (row-major, matching column order). LIMITS: keep each call under ~100 KB of rows (asset cap 2 MB) — for larger datasets create with a few rows and attach set_data_source, or append chunks via replace_asset_data.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"type":{"description":"Asset type, e.g. \"line\" (list_asset_types). Required unless template is given.","type":"string"},"template":{"description":"Start from a published chart (list_templates, or any chart id you were shown): its type and whole design are copied, your config merges on top (set title.text), and your data must supply the column ids the template expects — the error names them if not. The fastest way to a good-looking chart.","type":"string"},"title":{"description":"Convenience alias for config.title.text — the SAME field; if both are given, title wins. Color-span markup belongs in config.title.text.","type":"string"},"data":{"type":"object","properties":{"columns":{"minItems":1,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Column identifier referenced by encodings"},"type":{"type":"string","enum":["string","number","date","boolean"],"description":"date columns parse ISO-ish strings on their own: \"2024\", \"2024-03\", \"2024-03-15\", \"2024-Q2\", full ISO timestamps, or epoch numbers. Anything else needs dateFormat."},"label":{"description":"Display label; defaults to id","type":"string"},"dateFormat":{"description":"For date columns whose strings are NOT ISO-ish: a strftime-style parse hint tried first — tokens %Y %m %d %b %B %q %H %M %S, e.g. \"%d/%m/%Y\" for \"15/03/2024\", \"%b %Y\" for \"Mar 2024\". Removes the day/month guess; ISO-ish strings never need it.","type":"string"}},"required":["id","type"],"additionalProperties":false}},"rows":{"type":"array","items":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}]}},"description":"Row-major values, one array per row, order matching columns"}},"required":["columns","rows"],"additionalProperties":false,"description":"Typed columns + row-major rows"},"config":{"description":"Type-specific config (get_spec_schema). Must include encoding.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"brand":{"description":"Brand slug or id; omit for the default brand","type":"string"},"publish":{"description":"Publish immediately, so the returned url/pngUrl/embedUrl are live. Leave it off while you are still iterating on the preview.","type":"boolean"}},"required":["data"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"create_brand","description":"A brand styles every asset that references it — and the workspace default brand styles everything created without an explicit brand. Create one, then render any asset with brand: \"<slug>\" to see it applied. Style only: brands can never inject data or text content.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"slug":{"type":"string","pattern":"^[a-z0-9-]+$","description":"Stable handle, e.g. \"oscarleo\""},"name":{"type":"string"},"tokens":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Default settings for every chart using this brand — the SAME element shape as a chart config (document, title, description, source, notes, legend, badge, chart, palette, colors, shape) minus the words (no text/url) and the type-specific chart options — chart carries the style half of every element (xAxis.ticks.labels.font, yAxis.gridlines, seriesLabels.font…). Resolution: ENGINE_DEFAULTS <- brand <- the chart's own config. GET /api/brands/schema for the generated JSON Schema — do NOT guess key names: unknown keys are rejected (the error lists the valid keys at that path). Any element's font.family accepts ANY Google Fonts family by exact name (fetched and measured on first use)."},"isDefault":{"description":"Make this the workspace default applied to all brand-less assets","type":"boolean"}},"required":["slug","name","tokens"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"create_checkout_link","description":"Returns a URL that opens straight into Paddle checkout: no login, no account. It can only add credits to THIS workspace, so it is safe to share. Credits arrive seconds after payment (get_billing).","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"pack":{"description":"Pack size, default 10","type":"string","enum":["1","10","50"]}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"create_edit_link","description":"Returns a URL opening the visual tweak panel for ONE chart (text, colors, spacing, label nudges) with no login. PUBLISH FIRST: the editor saves and republishes a published chart, it cannot publish a draft. Scope: view, tweak, and republish that chart only — never delete, never spend credits, nothing else in the workspace. OFFER THIS whenever the human's request is aesthetic fine-tuning: handing over the wheel beats a nudge-by-nudge back-and-forth. The secret is shown once; links don't expire and can be revoked with revoke_edit_link.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"ast_..."}},"required":["id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"delete_asset","description":"Embeds go dark. Restorable via the REST API (POST /api/assets/{id}/restore).","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"duplicate_asset","description":"New id, new embed URLs. Useful for rebranding a chart for another audience.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"brand":{"type":"string"}},"required":["id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"fetch_data_source","description":"Runs one fetch on top of the schedule. Returns {ok, rows, published, error}.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"get_asset","description":"Full asset envelope. includePreview=true also returns an inline PNG of the current draft.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"ast_..."},"includeData":{"type":"boolean"},"includePreview":{"type":"boolean"}},"required":["id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"get_billing","description":"Hosting is free (unlimited badged charts, live updates included). One credit makes one chart premium forever: 2400px, SVG, your own branding, no badge. Updates are never metered.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"get_spec_schema","description":"Returns the JSON Schema for the type's config, the data schema, two worked examples (minimal + rich), and `defaults` — the complete config in force when nothing is set (every font, padding, color), which a brand and then the chart's own config merge over. Read this before your first create_asset of a given type; read a baseline value from defaults instead of guessing it.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"type":{"type":"string","description":"e.g. \"line\""}},"required":["type"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"list_asset_types","description":"Chart/table types you can create, with links to per-type schemas.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"list_assets","description":"Filterable list; rows elide config/data. Use get_asset for full bodies.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"type":{"type":"string"},"status":{"type":"string","enum":["draft","published","archived"]},"search":{"description":"Title substring","type":"string"},"limit":{"type":"integer","minimum":1,"maximum":200},"offset":{"type":"integer","minimum":0,"maximum":9007199254740991}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"list_brands","description":"Style-only token sets applied under your config. Pass a slug as `brand` on create_asset.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"list_geographies","description":"countries, us-states, us-states-pr (with Puerto Rico), us-counties, us-zip3, us-zips-<st> (one state's ZIP codes), and every country's states/provinces as <alpha-2>-regions (de-regions, ru-regions…). Pass id to get one geography's full region list — codes (ISO 3166-2 like DE-BY), names and aliases — and read it BEFORE naming regions in your data: unknown regions fail loudly. Set chart.geography to the id; chart.bounds crops.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"description":"A geography id for its region list, e.g. \"de-regions\"; omit to list all","type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"list_templates","description":"Templates are named by the NEED they answer (need, asks, tags), each with urls.png (show it to your human) and `columns`: the column ids your data must supply. Pass q with what the person asked for — \"map of brazil's states\", \"ranked bars with flags\", \"change between two years\" — and the list comes back ranked; take the first. Then create_asset with template: <id> and your data — the whole design comes along. Any chart id you were shown works as a template too, not only these.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"q":{"description":"What is needed, in the person's words","type":"string"},"type":{"description":"Only this asset type, e.g. \"bar\"","type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"publish_asset","description":"Makes the draft live. Returns urls: paste urls.embedIframe on iframe platforms; on Substack insert urls.png as an image and link it to urls.page. Pinned history: urls.png + '?v=N'.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"},"note":{"type":"string"},"premium":{"description":"Spend one paid credit to publish as premium (2400px, SVG, your branding, no badge) in this call","type":"boolean"}},"required":["id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"replace_asset_data","description":"THE core flow for refreshing numbers. publish defaults to auto: a published asset republishes immediately (every embed/PNG updates); a draft stays draft. Set publish=false to stage instead. LIMITS: an asset holds up to 2 MB of data, but keep each tool call under ~100 KB of rows — for larger datasets chunk with mode: \"append\" (publish: \"false\" on every chunk but the last), or skip inline entirely with set_data_source (the server fetches a URL, up to the full 2 MB).","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"},"mode":{"description":"append adds rows after the existing ones (same columns required) — the chunking path","type":"string","enum":["replace","append"]},"data":{"type":"object","properties":{"columns":{"minItems":1,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Column identifier referenced by encodings"},"type":{"type":"string","enum":["string","number","date","boolean"],"description":"date columns parse ISO-ish strings on their own: \"2024\", \"2024-03\", \"2024-03-15\", \"2024-Q2\", full ISO timestamps, or epoch numbers. Anything else needs dateFormat."},"label":{"description":"Display label; defaults to id","type":"string"},"dateFormat":{"description":"For date columns whose strings are NOT ISO-ish: a strftime-style parse hint tried first — tokens %Y %m %d %b %B %q %H %M %S, e.g. \"%d/%m/%Y\" for \"15/03/2024\", \"%b %Y\" for \"Mar 2024\". Removes the day/month guess; ISO-ish strings never need it.","type":"string"}},"required":["id","type"],"additionalProperties":false}},"rows":{"type":"array","items":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}]}},"description":"Row-major values, one array per row, order matching columns"}},"required":["columns","rows"],"additionalProperties":false,"description":"Typed columns + row-major rows. A date column whose strings are not ISO-ish takes dateFormat (e.g. \"%d/%m/%Y\")."},"config":{"description":"The full settings to store with the data — send it when the new columns differ from what the current config's encoding names, so both are checked together","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"publish":{"type":"string","enum":["auto","true","false"]},"note":{"description":"Publish note for the version history","type":"string"}},"required":["id","data"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"revoke_edit_link","description":"The URL stops working immediately. List links (prefixes + tokenIds) via GET /assets/{id}/edit-links.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"},"tokenId":{"type":"string","description":"etk_... from create_edit_link or the list"}},"required":["id","tokenId"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"set_data_source","description":"The chart refetches this URL (CSV or JSON) hourly/daily/weekly, replaces its rows, and republishes itself if published — refreshes are free, updates are never metered. Columns stay as defined on the asset: CSV headers / JSON object keys match column ids or labels case-insensitively, with spaces, underscores and hyphens interchangeable (births_per_woman matches a column labeled 'Births per woman'). Fetches once immediately and returns that outcome — check fetch.ok and fix fetch.error before moving on. get_asset shows dataSource + dataSourceState when a source is attached. Clear with clear_data_source.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string","description":"Public http(s) URL serving CSV, a JSON array of objects, or {rows:[[...]]}"},"refresh":{"description":"Default daily","type":"string","enum":["hourly","daily","weekly"]},"format":{"type":"string","enum":["auto","csv","json"]}},"required":["id","url"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"signup","description":"Creates a workspace and returns its API key (shown ONCE — keep it). Works on a keyless connection; every other tool needs the key. Then put the key where your client keeps it (plugin api_key setting, the Authorization header, or CHARTLINK_API_KEY for the npx bridge) and reconnect.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"name":{"description":"A name for the workspace (the project, the newsletter…)","type":"string"},"ref":{"description":"Where you heard of chartlink — free text","type":"string"}},"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"submit_feedback","description":"If a capability is missing, a schema fights you, or docs are wrong: record it here so the owner can fix it. One report per distinct issue, with a real description — reports get fixed same-day when they say what you tried, what you expected, and what happened (title-only reports can't be acted on).","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"type":{"type":"string","enum":["bug","feature_request","limitation","documentation_gap","other"]},"title":{"type":"string"},"description":{"type":"string","minLength":40,"description":"What you tried, what you expected, what happened instead — include the asset id if one is involved"},"assetId":{"type":"string"}},"required":["type","title","description"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"unpublish_asset","description":"Embeds and PNGs return 410 until republished.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"}},"required":["id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"update_asset","description":"configPatch is DEEP-MERGED: objects merge recursively, arrays replace wholesale, null clears a key — send only what changes. To REMOVE keys (drop document.aspect, delete one of texts[]) pass `config` instead: it REPLACES the whole config (get_asset returns the current one to edit). `data` (if given) replaces rows wholesale. Requires expectedVersion from the last envelope; on 409 re-merge onto the returned `current`. Updating does NOT publish by default — pass publish: true to publish in the same call, or call publish_asset separately.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string"},"expectedVersion":{"type":"integer","minimum":1,"maximum":9007199254740991},"publish":{"description":"true = publish immediately after this update succeeds (one call instead of two)","type":"boolean"},"configPatch":{"description":"Deep-merged onto the current config","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"config":{"description":"REPLACES the whole config — the way to remove keys; applied before configPatch if both are given","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"data":{"type":"object","properties":{"columns":{"minItems":1,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"description":"Column identifier referenced by encodings"},"type":{"type":"string","enum":["string","number","date","boolean"],"description":"date columns parse ISO-ish strings on their own: \"2024\", \"2024-03\", \"2024-03-15\", \"2024-Q2\", full ISO timestamps, or epoch numbers. Anything else needs dateFormat."},"label":{"description":"Display label; defaults to id","type":"string"},"dateFormat":{"description":"For date columns whose strings are NOT ISO-ish: a strftime-style parse hint tried first — tokens %Y %m %d %b %B %q %H %M %S, e.g. \"%d/%m/%Y\" for \"15/03/2024\", \"%b %Y\" for \"Mar 2024\". Removes the day/month guess; ISO-ish strings never need it.","type":"string"}},"required":["id","type"],"additionalProperties":false}},"rows":{"type":"array","items":{"type":"array","items":{"anyOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"null"}]}},"description":"Row-major values, one array per row, order matching columns"}},"required":["columns","rows"],"additionalProperties":false,"description":"Typed columns + row-major rows. A date column whose strings are not ISO-ish takes dateFormat (e.g. \"%d/%m/%Y\")."},"title":{"type":"string"},"brand":{"anyOf":[{"type":"string"},{"type":"null"}]},"showcase":{"description":"Offer this chart as a template in the public gallery and list_templates (once published). Workspace keys only.","type":"boolean"}},"required":["id","expectedVersion"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"update_brand","description":"Deep-merges tokens (objects merge, arrays replace, null clears). Drafts restyle immediately; published assets pick the change up on their next publish. Requires expectedVersion from list_brands.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"idOrSlug":{"type":"string"},"expectedVersion":{"type":"integer","minimum":1,"maximum":9007199254740991},"name":{"type":"string"},"tokens":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Default settings for every chart using this brand — the SAME element shape as a chart config (document, title, description, source, notes, legend, badge, chart, palette, colors, shape) minus the words (no text/url) and the type-specific chart options — chart carries the style half of every element (xAxis.ticks.labels.font, yAxis.gridlines, seriesLabels.font…). Resolution: ENGINE_DEFAULTS <- brand <- the chart's own config. GET /api/brands/schema for the generated JSON Schema — do NOT guess key names: unknown keys are rejected (the error lists the valid keys at that path). Any element's font.family accepts ANY Google Fonts family by exact name (fetched and measured on first use)."},"isDefault":{"type":"boolean"}},"required":["idOrSlug","expectedVersion"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"upgrade_to_premium","description":"Same URLs, instantly rebranded: badge removed, 2400px PNG and SVG unlocked, custom branding applies. Idempotent for already-premium charts.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"id":{"type":"string","description":"ast_..."}},"required":["id"],"$schema":"http://json-schema.org/draft-07/schema#"}},{"name":"whoami","description":"Returns key name + workspace. Call this first — non-destructive auth check. Everything you write is stamped createdBy/updatedBy = key name.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{},"$schema":"http://json-schema.org/draft-07/schema#"}}],"scan":{"score":91,"grade":"A","scanned_at":"2026-09-19T10:14:52.922Z","report":{"scannerVersion":"0.1.3","scannedAt":"2026-09-19T10:14:52.853Z","components":{"code":{"score":25,"max":25,"notes":["2 source files scanned"]},"reliability":{"score":20,"max":20,"notes":["remote reachable in 435ms"]},"poisoning":{"score":15,"max":15,"notes":["27 tool descriptions checked"]},"auth":{"score":8,"max":15,"notes":["API key sent as a header"]},"maintenance":{"score":15,"max":15,"notes":["last push 1 days ago"]},"identity":{"score":8,"max":10,"notes":["registry namespace matches repository owner","GitHub account older than a year"]}},"findings":[],"inputs":{"probes":[{"url":"https://chartlink.app/mcp","reachable":true,"authRequired":false,"latencyMs":435,"serverInfo":{"name":"chartlink","version":"0.1.0"}}],"packages":[{"registryType":"npm","identifier":"chartlink-mcp","version":"0.1.2","found":true,"license":"MIT","hasInstallScripts":false,"dependencyCount":1,"publishedAt":"2026-09-16T07:30:15.278Z","repositoryUrl":"git+https://github.com/oscarleoo/chartlink-agents.git"}],"repo":{"found":true,"owner":"oscarleoo","repo":"chartlink-agents","archived":false,"pushedAt":"2026-09-18T07:00:47Z","stars":0,"forks":0,"openIssues":0,"ownerType":"User","ownerAvatarUrl":"https://avatars.githubusercontent.com/u/43956652?v=4","ownerCreatedAt":"2018-10-08T11:27:56Z","license":"MIT"},"icon":{"url":"https://chartlink.app/icon.svg?icon.3plkqztizoi36.svg?dpl=dpl_33jV2v7nKAUPg3oEyec6sPerGyX1","source":"site"},"presence":{"stars":0,"forks":0,"downloadsWeek":null,"license":"MIT","lastPushAt":"2026-09-18T07:00:47.000Z","score":23}}}},"grade_history":[],"reviews":[]}