How the MCP server works
What Halo's MCP server is, its endpoint, tools and scopes, how it runs, its limits, and when to use it rather than the REST API.
The Halo MCP server lets an AI tool read an organization's datasets, charts, reports, websites and directory as the person who connected it. It speaks the Model Context Protocol, so any MCP client can use it. Every tool is read-only.
https://mcp.usehalo.net/mcpThis section is for people building on it. The MCP server for members and admins covers turning it on and connecting a tool as a person.
How it runs
Streamable HTTP, stateless. Each
POSTto/mcpcarries one JSON-RPC message and is authenticated on its own. There are no sessions, and nothing about one request carries into another. Answers are JSON, not event streams.No
GETorDELETE. With a valid token they answer405, which MCP clients expect from a server that doesn't stream. Without one they get the usual401, since the token is checked first.One message per request. Batches get
400, so one request can't carry many calls past the limits.Scoped listing.
tools/listshows only the tools a connection's scopes allow, and each call checks the scope again.The person, live. Every request loads the person's account, membership and role again, so a change applies on the next call. Tools run through the same services as Halo Data, with the person's access levels and row policies.
The MCP protocol has the details, with cURL.
Tools and scopes
A connection carries some of three scopes, which only narrow what the person's role allows. A token has the same three names as an API key, but they aren't interchangeable.
| Tool | Scope | Arguments | Does |
|---|---|---|---|
whoami | None | None | Says who the token acts for: the person, their organization and role, and the token's scopes |
list_datasets | datasets.read | search (optional) | Lists datasets the person can see: id, name, description, field count and connection |
describe_dataset | datasets.read | datasetId | A dataset's fields: name, label, role, type, default aggregate, format and description, plus its default date field |
query_dataset | data.query | query | Runs a read-only query as the person |
get_chart_data | data.query | chartId | Runs a saved chart's query, and returns the rows it draws with their columns |
list_goals | data.query | datasetId, search (both optional) | Lists goals and how each stands today, with the person's row policies |
read_goal | data.query | goalId | Reads what a goal targets, how it stands and its recent periods |
list_insights | data.query | datasetId (optional) | Lists the changes Halo flagged that the person can read, newest first |
list_charts | content.read | search (optional) | Lists saved charts: id, name, type, dataset and description |
read_chart | content.read | chartId | A saved chart's name, description, dataset and spec, not its data |
list_reports | content.read | search (optional) | Lists reports: id, title, status and workspace |
read_report | content.read | reportId, version (optional) | A report's document: its pages and elements, with each chart's spec or saved chart id, and its text |
list_websites | content.read | search (optional) | Lists websites: id, name, whether readers can reach it, and how many pages it has |
read_website | content.read | websiteId | A website's live release, custom domains, pages by path and title, and collections by name |
list_listings | content.read | search, kind, status, region (all optional) | Lists the directory's listings: id, name, kind, status and city |
read_listing | content.read | listingId | One listing, with the phone number its page gives visitors and no email |
list_businesses | content.read | search, status, region (all optional) | Lists businesses: id, name, status, listings and city |
read_business | content.read | businessId | One business and its listings, with nobody's contact details |
Each tool is marked readOnlyHint and idempotentHint. The full input schemas are at dev.usehalo.net/reference/mcp.
query_dataset takes a query much like the REST body, with the dataset's id inside it:
{
"query": {
"datasetId": "0195f3a2-…",
"dimensions": [{ "field": "region" }],
"measures": [{ "field": "revenue" }],
"limit": 50
}
}Its schema also lists specVersion, which Halo fills in if you leave it out.
What comes back
A tool's answer is text for a model to read, wrapped in a <halo-data> fence that says the content is data, not instructions. See the fence. Treat what's inside as untrusted: Build an agent shows how.
Limits
| Limit | Amount |
|---|---|
| Requests a minute, per address | 600 |
| Requests a minute, per connection | 240 |
| Tool calls a minute, per connection | 120 |
| Tool calls a minute, per person | 300 |
| Tool calls running at once, per connection | 4 on each of Halo's API processes, so the true ceiling can be higher |
| Data rows a day, per person in each organization | 20,000 |
| Rows a query | 200, or 1,000 where the organization has Let Halo AI read individual rows on |
| Characters in a query's answer | About 24,000, then the rows are cut with a note |
Characters in read_report's answer | 48,000, then the document is cut with a note |
| Items in a list | 30 from list_goals, 60 from list_datasets, 100 from list_charts, list_reports, list_websites, list_listings and list_businesses, then a line says more match. Search with more specific words |
| Request size | 1 MB |
Paging (offset, keyset and cursor) is refused unless the organization allows individual rows, so paging can't walk past the row cap. Rows of Marketplace data are refused too, though their fields can be read. These tools run on your own AI provider and don't use Halo's AI credits.
Before it works
It's off until an Admin turns it on. Then, for every request, all of these must hold:
Halo AI is on for the organization, and its plan includes it.
The person's role has
ai.use.An Admin has turned on Let members connect their own AI tools.
Halo hasn't paused the organization or its tools, and its Admins haven't paused the member.
The member is active, and meets the organization's sign-in and two-factor rules.
What a tool reads goes to that tool and its AI provider, outside Halo. The admin guide has the steps.
The Halo MCP server or REST
| MCP server | REST API | |
|---|---|---|
| For | A model choosing what to read, in a conversation or agent | Your own code, reading what it was written to read |
| Credential | hpat_… or OAuth hmat_… | hak_… |
| Made in | Halo Data, or an app's sign-in | The portal |
| By default | Off, until an Admin turns it on | Off, until an Admin turns it on |
| The role needs | ai.use | api.use, and data.export for rows |
| Answers in | Text for a model | JSON |
| Rows | 200 a query, or 1,000 where allowed, and 20,000 a day | 1,000 a request, and 100,000 to 2,000,000 a day by plan |
| Paging | Only where individual rows are allowed | Always |
| Sign-in for other people's tools | OAuth, with consent per connection | None. A key is made by its person |
Use the Halo MCP server when a model decides what to ask. Use REST when your code does, or when you need many rows. Both read as a person and both are read-only, so neither widens what that person can see.