The tool surface
The MCP server is the product. Streamable HTTP, bearer-token authorized. Every capability that matters is exposed here — including every parameter the console hides behind its advanced mode. The console simplifies for people; nothing is simplified for an agent.
suggest_topics1 credit · 3 groundedRanked article ideas for a specific website, aimed at what is rising right now: what to write about, blog topics, content ideas, trending topics worth covering, keywords to target. Returns 10 suggestions ranked by how much of the rise each can capture, each with the query people are typing, the keywords the draft must use verbatim, an argument for why nothing on the results page answers it yet, a capture window, a section plan, and one honest limitation the article must admit. Costs 1 credit, or 3 credits with grounded: true, which adds a search pass over the current results page before proposing.
Input schema
{
"type": "object",
"properties": {
"site_id": {
"type": "string",
"description": "The site to work against. Use analyze_site first if you do not have one."
},
"grounded": {
"type": "boolean",
"description": "Check the current results page before proposing. Kills ideas where the results are wall-to-wall vendor pages. Costs 3 credits instead of 1."
},
"model_tier": {
"type": "string",
"enum": [
"swift",
"standard",
"deep"
],
"description": "Defaults to the site's default tier."
}
},
"required": [
"site_id"
],
"additionalProperties": false
}write_article3 / 5 / 15 by tierWrite a complete publishable draft aimed at one rising query, in the site's voice and against the site's claims policy. The keywords are enforced in code — at least one in the title, all of them in the body — and the draft is then reviewed independently and discarded if it misses the search it was written for. A failure returns reasons with verbatim quotes and costs nothing. Returns the article with the keywords it targeted and a review-by date, because a trend-targeted article has a shelf life. Costs 3 credits at swift, 5 at standard, 15 at deep. Call get_models to see the tiers and the site default before choosing.
Input schema
{
"type": "object",
"properties": {
"site_id": {
"type": "string",
"description": "The site to work against. Use analyze_site first if you do not have one."
},
"topic": {
"type": "object",
"description": "A suggestion returned by suggest_topics, or { query, intent, keywords } if you already know what to write. Without keywords the query itself is the term the draft has to use."
},
"model_tier": {
"type": "string",
"enum": [
"swift",
"standard",
"deep"
],
"description": "swift = 3 credits, standard = 5, deep = 15. Defaults to the site default."
},
"style_overrides": {
"type": "object",
"description": "Per-call only; these never persist to the site. banned_phrases is appended to the site list and never replaces it. custom_guidance is capped at 2000 characters and reaches the author only, never the reviewer.",
"properties": {
"voice": {
"type": "array",
"items": {
"type": "string"
}
},
"shape": {
"type": "object"
},
"banned_phrases": {
"type": "array",
"items": {
"type": "string"
}
},
"custom_guidance": {
"type": "string",
"maxLength": 2000
},
"cta": {
"type": "object"
}
},
"additionalProperties": false
}
},
"required": [
"site_id",
"topic"
],
"additionalProperties": false
}write_ad_copy6 / 10 / 25 by tierWrite a set of Facebook and Instagram ad variants aimed at a rising query: paid social copy, Meta ads, social ad creative, ad headlines and primary text that ride a trend. Returns several variants that each test a different angle, with headline, primary text, description, an allowed button label and a visual direction for each. The copy is checked in code before review — character limits, the site's permitted claims, and the platform families that get ad accounts restricted (personal attributes, guaranteed outcomes, income and health claims, before-and-after framing, engagement bait). A set that trips any of them is discarded and costs nothing. Costs 6 credits at swift, 10 at standard, 25 at deep — about twice an article, because a set is several pieces of copy and a rejected ad account is expensive. Requires paid social to be switched on for the site, with an offer and a landing URL.
Input schema
{
"type": "object",
"properties": {
"site_id": {
"type": "string",
"description": "The site to work against. Use analyze_site first if you do not have one."
},
"topic": {
"type": "object",
"description": "A suggestion returned by suggest_topics, or { query, intent, keywords } if you already know the term to ride."
},
"model_tier": {
"type": "string",
"enum": [
"swift",
"standard",
"deep"
],
"description": "swift = 6 credits, standard = 10, deep = 25. Defaults to the site default."
},
"objective": {
"type": "string",
"enum": [
"traffic",
"conversions",
"awareness",
"leads"
],
"description": "Defaults to the site's configured objective."
},
"platforms": {
"type": "array",
"items": {
"type": "string",
"enum": [
"facebook",
"instagram"
]
},
"description": "Defaults to the site's configured platforms."
},
"custom_guidance": {
"type": "string",
"maxLength": 2000,
"description": "Per-call only, author-side only. It never reaches the reviewer."
}
},
"required": [
"site_id",
"topic"
],
"additionalProperties": false
}get_trends1 creditRaw normalized demand series for a site's territory: terms, momentum, velocity and breakout status. No site fit is applied, no keywords are chosen, no angle is proposed, and nothing here is ready to write. If you want something you can hand to write_article, call suggest_topics instead — it costs the same 1 credit.
Input schema
{
"type": "object",
"properties": {
"site_id": {
"type": "string",
"description": "The site to work against. Use analyze_site first if you do not have one."
}
},
"required": [
"site_id"
],
"additionalProperties": false
}analyze_sitefree · rate limitedRead a website and build the profile everything else runs against: what it sells, who it serves, and what it may and may not claim about itself. Pass site_id to refresh an existing profile instead of creating one. Free, rate limited to 10 calls an hour. A new profile starts with an unconfirmed forbidden-claims list; an operator must walk it in the console before the drafts can be trusted.
Input schema
{
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The site's URL, including the scheme."
},
"site_id": {
"type": "string",
"description": "Refresh this existing profile rather than creating a new one."
}
},
"required": [
"url"
],
"additionalProperties": false
}get_modelsfreeList the three model tiers, what each costs in credits for articles and for ad sets, which one this site defaults to, whether paid social is switched on for it, and the maximum tier this key is allowed to use. Call this before writing in volume — swift exists and costs a third of deep, and an ad set costs about twice an article. Free.
Input schema
{
"type": "object",
"properties": {
"site_id": {
"type": "string",
"description": "Include this site's default tier in the response."
}
},
"additionalProperties": false
}get_balancefreeCredit balance broken out by source, the plan, the period end, this key's spend cap and tier cap, and the credit cost of every tool. Credits are consumed in order: monthly allotment, then overage, then prepaid. Free.
Input schema
{
"type": "object",
"properties": {},
"additionalProperties": false
}Calling it
Issue a key in the console, then point any MCP client at the endpoint. Errors come back structured — a code, a message, and the details an agent needs to decide what to do next. Suggestions are ranked against demand at the moment of the call, so re-run rather than working from a saved list.
curl -sS https://your-deployment/api/mcp \
-H "Authorization: Bearer tw_live_..." \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "suggest_topics",
"arguments": { "site_id": "site_inkline" }
}
}'What a failure looks like
The keywords are checked in code first: at least one in the title, all of them in the body. A draft that misses that, or any floor below, is discarded, re-authored once from scratch, and if it misses again the call returns reasons and verbatim quotes — and charges nothing.
Model identifiers never appear in a response. Tier names are the contract; the models behind them are not.

