Building an Agent Against the ChartHop MCP Server
building an agent against the charthop mcp server this guide is for developers connecting an ai agent to charthop over the model context protocol (mcp) https //modelcontextprotocol io it covers how to connect and authenticate, what tools are available, how to call them, and the security model you're operating inside prerequisites mcp server access requires the ai pro module ensure ai pro is enabled in admin > ai before proceeding if you're using a supported mcp client (claude, chatgpt connectors, cursor, etc ), most of the oauth handshake is automatic โ you paste a url and approve a consent screen if you're building a client from scratch, the connecting /#connecting section documents the full flow tl;dr transport json rpc 2 0 over http post (streamable http) request/response only (no sse stream) auth oauth 2 0 with pkce and dynamic client identification (cimd) you end up with a bearer token scoped to one agent and one org connection url https //\<host>/mcp/{agent} (org chosen at consent time) or https //\<host>/mcp/{agent}/{orgid} (org fixed in the url) tools four search , list , detail (all read only), and askagent (delegate a task to a charthop agent) writes off by default the three data access tools are read only by design; actual mutation happens only through askagent , and only when the agent install has writes enabled connecting connection url shapes there are two url patterns {agent} is the slug of the charthop ai agent you're connecting to (an org can publish more than one agent) pattern when to use post /mcp/{agent} org less the org is selected during the oauth consent step use this for marketplace or directory listings where the url can't hardcode a customer post /mcp/{agent}/{orgid} org scoped the org is fixed in the url {orgid} may be an org id or slug get on either path returns 405 method not allowed โ the server does not offer an sse stream all traffic is request/response post the oauth flow charthop implements oauth 2 0 with pkce (s256, mandatory) and cimd (client identification by metadata document) instead of pre registered client secrets your client is identified by a url to a hosted client metadata document rather than a client id / client secret you register ahead of time a compliant mcp client performs these steps automatically in order 1\ discover the authorization server starting from just the mcp url, fetch the protected resource metadata (rfc 9728) get / well known/oauth protected resource/mcp/{agent}\[/{orgid}] โ { resource, authorization servers, bearer methods supported } then the authorization server metadata (rfc 8414) at the advertised location get / well known/oauth authorization server/mcp/{agent}\[/{orgid}] 2\ fetch grant info for the consent screen get /oauth/mcp/grant info?clientid=\<cimd url>\&resource=\<resource>\&redirecturi=\<redirect> clientid โ the url of your hosted client metadata document (e g https //claude ai/ well known/oauth client metadata json ) resource โ the mcp server url being authorized (rfc 8707) the response describes your client, the signed in user, and โ for an org less url โ the list of candidate orgs the user can pick from 3\ user approves the charthop consent screen posts the approval post /oauth/mcp/approve { "clientid" "\<cimd url>", "redirecturi" "\<redirect>", "codechallenge" "\<s256 hash>", "codechallengemethod" "s256", "resource" "\<resource url>", "state" "\<opaque>", "orgid" "\<selected org>" // only for the org less url } โ { "redirecturl" "\<redirect>?code= \&state= \&iss= " } the authorization code is bound to the user, agent, org, pkce challenge, and redirect uri, and carries the mcp and sensitive scopes 4\ exchange the code for an access token (pkce verifier required) post /oauth/token grant type=authorization code code=\<code> client id=\<cimd url> redirect uri=\<redirect> code verifier=\<pkce verifier> resource=\<resource url> // rfc 8707, optional โ { "access token" " ", "token type" "bearer", "expires in" } the cimd document at clientid is validated server side https only, no redirects, size capped, fetched through an egress proxy (ssrf protection), and the redirect uri must be listed in it loopback redirect uris match with port flexibility per rfc 8252 ยง7 3 so native clients can use an ephemeral local port using the token send it as a bearer token on every mcp request authorization bearer \<access token> the token is audience bound to the agent and org bound (the org you chose at consent, or the org in the url) a token minted for one agent/org cannot be replayed against another the sensitive scope controls whether sensitive fields (e g compensation) come back unmasked โ subject to the agent's own permissions the protocol versions the server speaks multiple mcp protocol versions and negotiates per client version notes 2026 07 28 newest stateless declared per request (see below), no initialize handshake 2025 06 18 latest legacy version no json rpc batching 2025 03 26 legacy supports batching 2024 11 05 legacy supports batching legacy clients send an initialize request; the server replies with the negotiated protocolversion , capabilities , and serverinfo , and you send an mcp protocol version header on subsequent requests if a legacy initialize asks for 2026 07 28 , it's clamped down to the latest legacy version โ the stateless path is opted into differently stateless ( 2026 07 28 ) clients skip initialize entirely and declare the version on each request under params meta\["io modelcontextprotocol/protocolversion"] discover support with the server/discover method, which returns supportedversions (newest first) content type the message endpoint accepts any content type โ some clients (e g chatgpt's connector) post without application/json the body is parsed as json rpc regardless, and parse failures come back as json rpc errors rather than http 415 responses are application/json notifications (json rpc messages with no id ) get 202 accepted with no body methods method purpose initialize legacy handshake returns protocolversion , capabilities , serverinfo , optional instructions server/discover 2026 07 28 discovery returns supportedversions , capabilities , identity, optional instructions ping health check returns {} tools/list list the available tools (see below) tools/call invoke a tool tools/list and server/discover carry cache hints ( ttlms , cachescope "private" ) โ the tool set is auth and agent specific, so cache it briefly and per credential tools are returned in a stable (name sorted) order server instructions if the org's admin has configured an "mcp instructions" hint, its text is returned in the instructions field of initialize / server/discover treat it as system guidance for how to use this particular org's tools; it's omitted when unset the tools four tools are exposed search , list , and detail are read only data access tools askagent delegates a whole task to a charthop agent each tool in tools/list carries mcp annotations you can use to drive ui or warnings readonlyhint , destructivehint , idempotenthint , openworldhint search find entities by name param type req description query string yes name to match against entitytypes string\[] which types to search; omit to search all enum persons , jobs , fields , contents , functions , appusers , scenarios , compbands , compreviews , reports , users , orgs , customers includeformer boolean include former employees as if current default false use search to turn a name into an id, then feed that id to detail or a list filter list list entities of a type as tabular data, with filtering, pagination, sorting, and grouping/aggregation param type req description entitytype enum yes the entity type to list (see entity types /#entity types ) filter string cql filter expression, e g dept\ engineering or status\ pending columns string\[] columns to return omit for the type's default columns request specifics like basecomp , startdate , email , manager sort string column to sort by; prefix for descending (e g basecomp ) limit integer max results default 100, max 200 next string pagination cursor from a prior list result groupby string column to group by (e g headcount by department ) when set, next / limit / sort are ignored โ aggregation runs over the whole filtered set aggfn string comma separated aggregations for groupby count is always included; add sum , mean , min , max startdate / enddate string inclusive range for date aware types yyyy mm dd or iso 8601 datetime; supports relative dates ( today , 30d , startofmonth , endofmonth ) scenarioid string list within a planning scenario instead of the live org pagination call again with next set to the cursor from the previous result until it's absent detail fetch full structured detail for up to 20 entities by id param type req description entitytype enum yes the entity type (see entity types /#entity types ) ids string\[] yes entity ids (24 char hex objectids) max 20 startdate / enddate string range for date aware entities (reports/dashboards), yyyy mm dd interval enum for date aware entities day , week , month , quarter , fiscal quarter , year , fiscal year scenarioid string view the entity within a scenario over mcp, detail always returns structured metadata id chaining caveat the ids you pass to detail must come from a search or list result in the same scope (org, scenario, date range) ids are not globally stable across scenarios โ pull the id from a list / search that used the same scenarioid /date window you're about to query askagent delegate a question or task to a charthop ai agent the agent runs autonomously with its own tools and permissions, then returns a result use this when you'd rather hand off "answer this hr/org data question" than orchestrate list / detail yourself it's also the only path to mutations over mcp param type req description prompt string yes the question or task for the agent agent enum which agent to ask enumerated dynamically from what your access allows defaults to the org's default agent readonly boolean if true (default), the agent is restricted to read only tools set false to allow data modifying tools โ subject to the agent's own permissions and the install's write setting (see below) complexity enum reasoning effort high , medium , low higher uses a more capable model defaults to the agent's configured tier calling a tool tools/call takes the tool name and an arguments object { "jsonrpc" "2 0", "id" 42, "method" "tools/call", "params" { "name" "list", "arguments" { "entitytype" "person", "filter" "dept\ engineering", "columns" \["name", "title", "manager", "startdate"], "sort" " startdate", "limit" 50 } } } the result is standard mcp tool content โ a content array of text blocks, with iserror set when the call failed { "jsonrpc" "2 0", "id" 42, "result" { "content" \[{ "type" "text", "text" "\<display formatted table>" }], "iserror" false } } tool output is already display formatted server side (tabular data rendered to text) a failed call returns iserror true with an explanatory message rather than a json rpc transport error entity types list and detail accept these entitytype values (over mcp they're exposed as an enum, and the set is filtered to what your access and the org's configuration allow โ e g timeoff is hidden when the time off tab is disabled) person , job , group , field , report , report chart , event , file , form , task , template , content , scenario , table , process , action , calendar , calendar entry , comp band , comp review , assessment , goal , goal target , goal progress , goal type , stock grant , timeoff , transcript in addition, custom tables can be queried by name (use the table's name as the entitytype , and table to discover table metadata) security model understanding this will save you debugging time when a call returns less than you expect read only by default search / list / detail cannot mutate anything writes are gated twice a mutation can only happen through askagent with readonly false , and only if the agent install has enablemcpwrite turned on if it's off, write attempts are rejected at the mcp edge with a clear message this is defense in depth layered on top of the agent's role/policy โ it never grants access beyond what the agent's role already allows mcp must be enabled on the install ( enablemcpserver ) for the server to respond at all everything runs as the agent, on behalf of the authenticated user data visibility (including whether sensitive fields are masked) is the intersection of the agent's role/policies and the user's access โ not the raw database two different tokens against the same org can legitimately see different results scenario/date scope is explicit if you don't pass scenarioid or a date range, you get the live org as of today chain ids within a consistent scope a minimal agent loop connect and authenticate obtain a bearer token for {agent} and org initialize (or server/discover for stateless) read instructions tools/list register search , list , detail , askagent with your model let the model call tools resolve names to ids with search pull tabular/aggregate data with list (paginate via next ) expand specific records with detail (up to 20 ids, matching scope) hand off open ended tasks โ or any write โ to askagent feed each tool result's content text back to the model and continue
