Connecting AI Agents with MCP
AI assistants and your own agents can search our people and healthcare data through our MCP server. MCP (Model Context Protocol) is the standard AI assistants use to call outside tools. The server is at https://api.aggknowledgeai.com/mcp/v1.
MCP access is part of your contract. If your organization can't connect, ask your AggKnowledge account team to turn it on.
Two ways to connect#
| Who connects | Signs in with | Seat | What it may do |
|---|---|---|---|
| A person, from Claude, ChatGPT, or Cursor | Their AggKnowledge sign-in | Uses their seat, assigned at first sign-in if they don't have one | What their role allows in the app |
| Your own agent or backend job | An agent credential: a client ID and secret | Uses no seat | Only the permissions you grant the credential |
Connect an AI assistant#
Add the server URL, then sign in when asked. The assistant opens our sign-in page as soon as you add the server. Sign in with your AggKnowledge account, and the assistant acts with your role’s permissions from then on.
Signing in can use a seat. Someone who has never signed in to the app is assigned a seat the first time they connect. If your organization has no seats left, sign-in is refused.
- Claude: add a custom connector with the server URL.
- ChatGPT: add a custom MCP server with the server URL. Which ChatGPT plans allow this varies, and a Business or Enterprise workspace admin may need to turn it on first.
- Cursor: add the server to
mcp.json.
{
"mcpServers": {
"aggknowledge": {
"url": "https://api.aggknowledgeai.com/mcp/v1"
}
}
}Connect your own agent#
An agent signs in with a client ID and secret. It uses no person's account: it exchanges them for an access token through OAuth client credentials (the standard sign-in for software acting on its own), then sends that token with every request.
https://api.aggknowledgeai.com/mcp/v1 and send the token as a bearer token in the Authorization header.curl -X POST https://id.aggknowledge.ai/oauth2/token \
-d grant_type=client_credentials \
-d client_id="$AGGK_AGENT_CLIENT_ID" \
-d client_secret="$AGGK_AGENT_SECRET"{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}Rotate a secret without downtime. Add a new secret, move the agent over to it, then delete the old one. Both secrets work until you delete one.
Permission changes take up to an hour. A token carries the permissions the credential had when it was issued, so a change applies from the agent’s next token.
Deleting a credential cuts the agent off within a minute. New token requests fail straight away, and tokens it already holds stop working shortly after.
The tools#
| Tool | What it does | Cost | Agent permission |
|---|---|---|---|
ping | Checks that the server is reachable. | Not billed | None |
list_filter_values | Lists the valid values for filters that take fixed values, such as industry. | Not billed | None |
discover_count | Counts the people a Discover search matches and returns a queryId for it. | Not billed | discover:search |
discover_people | Returns a page of the people a queryId matched, up to 100 per page. | 1 credit per queryId | discover:search |
enrich_person | Returns one person's record, bio included, by person ID or profile URL. | 1 credit per request that finds the person | person:enrich |
match_hcp | Finds healthcare professionals (HCPs) by name, identifier, or attributes, best match first. | Not billed | healthcare:enrich |
get_hcp | Returns an HCP's record by HCP ID. | 1 healthcare credit per HCP per billing period | healthcare:enrich |
get_hcp_focus_areas | Ranks what an HCP treats, prescribes, performs, researches, or runs trials on, largest first. | 1 healthcare credit per HCP per billing period | healthcare:enrich |
get_hcp_publications | Returns an HCP's publications, newest first, up to 100 per page. | 1 healthcare credit per HCP per billing period | healthcare:enrich |
get_hcp_trials | Returns the clinical trials an HCP is linked to, newest first, up to 100 per page. | 1 healthcare credit per HCP per billing period | healthcare:enrich |
get_hcp_congresses | Returns an HCP's congress presentations, newest first, up to 100 per page. | 1 healthcare credit per HCP per billing period | healthcare:enrich |
browse_healthcare_taxonomy | Walks or searches the diagnosis and procedure category trees. | Not billed | None |
match_company | Finds companies by name, website, profile URL, or ticker and exchange, best match first. | 1 credit per request with results | company:search |
get_company | Returns a company's record by company ID, website, or profile URL. | 1 credit per request that finds the company | company:enrich |
get_company_headcount | Returns a company's monthly employee count, hires, and departures. | Not billed | company:headcount:enrich |
get_company_relationships | Lists a company's customers or vendors, up to 500 per page. | 1 credit per company per billing period | company:relationships:enrich |
search_product_catalog | Finds products by name, or pages through every product or category, for Discover's product filters. | Not billed | vendorgraphic:company:enrich or vendorgraphic:person:enrich |
request_contact_info | Requests work email, personal email, or phone for up to 25 people or HCPs. | Charged from your contact balance | The contact:…:enrich permission for each channel |
get_contact_info | Reads a contact request's status and results. | Not billed | contact:view, or any contact enrich permission |
get_person_lists | Lists your person lists, or one list's members as person IDs. | Not billed | None |
update_person_list | Creates, renames, or deletes a list, or adds or removes members. | Not billed | None |
enrich_people | Enriches up to 500 people by person ID or profile URL as one batch. | 1 credit per person enriched | person:enrich |
get_enriched_people | Reads an enrich_people batch, a page of people at a time. | Not billed | person:enrich |
submit_deep_match | Resolves up to 1,000 rows of partial signals to a person or HCP as one batch. | 1 credit per row that resolves | person:deep_match |
get_deep_match | Reads a submit_deep_match batch, a page of rows at a time. | Not billed | person:deep_match |
How a search runs#
Count first, then read the people. The filters are the ones Discover search uses. This search finds VPs and C-level leaders in engineering at software companies:
{
"or": [
{
"company": { "industry": { "or": ["computer software"] } },
"workExperience": {
"seniority": { "or": ["vp", "cxo"] },
"role": { "or": ["engineering"] }
}
}
]
}{
"count": 3512,
"isApproximate": false,
"queryId": "8f14e45fceea167a5a36dedd4bea2543"
}Pass the queryId to discover_people to read the matches a page at a time. page starts at 0 and size defaults to 25.
{
"queryId": "8f14e45fceea167a5a36dedd4bea2543",
"page": 0,
"size": 25
}- A
queryIdexpires after 30 minutes unused, and after 24 hours in any case. - Only the first 5,000 people of a search can be read. Narrow the filters to reach the rest.
- Counts above 40,000 are approximate, and
isApproximatesays so. - A filter value we don't recognize is refused. The error lists the closest valid values, if any are close. Call
list_filter_valuesto see them all.
Find an HCP and what they work on#
Match first to get an HCP ID. Your contract needs healthcare enrichment. Pass any of a name, NPI, email, person ID, specialty, organization, city, state, or country, one value each. limit defaults to 5, up to 25.
{
"name": "Jay Patel",
"specialty": "cardiology"
}{
"hcps": [
{
"affiliations": [
{
"aggkHcoId": "187892874929524688",
"name": "Wentworth - Douglass Hospital"
}
],
"aggkHcpId": "169067854124029421",
"aggkPersonId": "731209846",
"designations": ["MD"],
"firstName": "Jay",
"lastName": "Patel",
"matchedOn": ["fullName", "specialties"],
"npi": "1033479100",
"score": 41.7,
"specialties": [{ "name": "Cardiovascular Disease" }]
}
]
}- An identifier settles the match. With an NPI, email, or person ID, the other criteria only rank the results. Without one, pass a name, which must match fuzzily.
- Specialty and place alone don't match. A call with neither a name nor an identifier is refused, so the tool never presents a loose attribute hit as the person you meant.
- Pass a person ID to find the HCP behind a person from
discover_peopleorenrich_person.
Then rank their focus areas along one axis. get_hcp_focus_areas takes an HCP ID and an axis, and returns the same rows as the matching v3 endpoint, largest first:
axis | Ranked by |
|---|---|
conditions_treated | Claims |
procedures_performed | Claims |
drugs_prescribed | Pharmacy fills |
drugs_administered | Administrations in the clinic, such as infusions |
research_topics | Publications |
conditions_investigated | Trials naming the HCP |
drugs_investigated | Trials naming the HCP |
{
"aggkHcpId": "169067854124029421",
"axis": "conditions_treated",
"fromYear": 2023,
"limit": 10
}{
"aggkHcpId": "169067854124029421",
"axis": "conditions_treated",
"results": [
{
"bodySystem": "Diseases of the circulatory system",
"bodySystemCode": "CIR",
"ccsrCode": "CIR019",
"claimCount": 1742,
"indication": "Heart failure",
"indicationCui": "C0018802",
"patientMonths": 1311
}
],
"nextCursor": "bzoxMDA"
}- Check both drug axes.
drugs_prescribedanddrugs_administeredreturn the same drugs, ranked by different counts. A drug given in the clinic, like a cancer infusion, has few pharmacy fills and can fall off the end ofdrugs_prescribed. fromYearandtoYearlimit the years counted, andminCountdrops rows counted fewer times than that over those years.- Per-year counts are left out by default. Set
includeYearlytotrueto get them. - A cursor is tied to its filters. Change the axis, years, or
minCountand the oldnextCursoris refused; start again without one.
Read an HCP's publications#
Pass an HCP ID to get its publications, newest first. Your contract needs healthcare enrichment. limit defaults to 20. Pass the nextCursor (a pointer to the next page) of one page as cursor to read the next.
{
"aggkHcpId": "169067854124029421",
"limit": 20
}{
"aggkHcpId": "169067854124029421",
"publications": [
{
"citationCount": 47,
"datePublished": "2024-03-12",
"doi": "10.1000/182",
"isFirstAuthor": true,
"isLastAuthor": true,
"journal": "Journal of the American College of Cardiology",
"pubId": "W2126798093",
"publicationTypes": ["Journal Article"],
"title": "Cardiovascular Outcomes of GLP-1 Receptor Agonist Therapy in Heart Failure",
"url": "https://doi.org/10.1000/182"
}
],
"nextCursor": "W1sibCIsIjE3MTAyMDE2MDAwMDAiXSxbInMiLCJXMjEyNjc5ODA5MyJdXQ"
}- Abstracts are left out by default, because they're long. Set
includeAbstractstotrueto get them. - A cursor works only with
get_hcp_publications. The REST publications endpoint orders results differently, so its cursors aren't interchangeable. - Trials and congresses page the same way, newest first by start date, with
get_hcp_trialsandget_hcp_congresses. Trial summaries, eligibility criteria, and session descriptions are left out unless you setincludeDetailstotrue.
Enrich a person#
Pass a person ID or a profile URL to get that person's record. Pass one of the two, never both. Your contract needs person enrichment. The record has the same fields as a v3 person enrichment returns (see Persons), with empty fields left out.
{
"linkedinUrl": "https://www.linkedin.com/in/danielentrup"
}{
"person": {
"aggkPersonId": "590654273",
"bio": "Co-founder of AggKnowledge, a data management and enrichment business purpose-built for knowledge networks.",
"dateLastUpdated": "2026-07-05",
"firstName": "dan",
"lastName": "entrup",
"locationCity": "new york",
"locationCountry": "united states"
}
}biois the person's own profile summary, not text we write.- A stale record is refreshed before it's returned.
dateLastUpdatedsays when it last was. If a refresh fails, you get the stored record with awarningsaying so.
Look up a company#
Match first to get a company ID. Pass a company name, website, profile URL, or a ticker with its exchange (a MIC code such as xnas). country only ranks the results. limit defaults to 5, up to 25.
get_companyleaves the long lists out by default. SetincludeDetailstotruefor subsidiaries, competitors, products and services offered, and private equity investors.wantnames whatget_company_relationshipsreturns:customersare the companies that buy from this one,vendorsthe companies it buys from. A cursor from one side is refused on the other.- Find product and category IDs with
search_product_catalog, then filter adiscover_countsearch on them withaggkProductIdorcategoryId.
Request contact info#
Request, then read the results with the batch ID. request_contact_info returns contact we already hold straight away, plus a batchId. The rest is looked up and usually ready within minutes: pass the batchId to get_contact_info to read it.
- Each channel applies to every ID in the request. Ask for
workEmail,personalEmail, orphone, for up to 25 person IDs and HCP IDs in total. - HCPs have work email only. Ask for personal email or phone for people in a separate request.
- A request runs as an ordinary contact batch, so it sends your contact webhook when it finishes, if you've set one up.
Manage person lists#
A person sees their own lists; an agent sees the organization's. Someone connected through an AI assistant works with the lists they see in the app, private ones included. An agent credential works with the lists shared with everyone, as an API key does.
update_person_listtakes anaction:create,rename,delete,add_members, orremove_members.- Warning:
deletecan't be undone, and needs no permission beyond access to the server. Grant an agent credential only to agents you trust with your lists. - Add members by person ID or profile URL, up to 1,000 per request. Each result says whether the person was added.
Run a batch#
Submit the batch, then read it a page at a time. enrich_people and submit_deep_match return a batchId straight away; a batch usually finishes within minutes. Pass the batchId to get_enriched_people or get_deep_match, which report status and page the results with nextCursor.
- Pages are small by default: 10 people, up to 50, because each person record is long; 50 Deep Match rows, up to 200.
- A Deep Match row takes any of
fullName,stateOrProvince,country,emails,phones,profileUrl,employerNames, andnpis. Every field is scored together, and an email is the strongest signal. Results carry each row'srowIndex, from 0. - Each row's evidence is left out by default. Set
includeDetailstotrueonget_deep_matchfor matched criteria and candidates. - A batch runs like one submitted through the API, so it sends your enrichment or Deep Match webhook when it finishes, if you've set one up.
Billing#
A search costs 1 credit, however many pages you read. We bill it on the first page that returns people, and a search that matches nobody isn’t billed. Credits come from the same allowance as Discover search in the app, and we report MCP usage separately.
An HCP costs 1 healthcare credit per billing period, across every HCP tool. We bill on the first request that returns something about them. Every later page, and every later request for the same HCP that period, from any HCP tool, isn't billed. A request that returns nothing isn't billed, and match_hcp is never billed.
Each enrichment that finds the person costs 1 credit, from the same allowance as the person enrichment API. Enriching the same person twice costs 2 credits. A request that finds no one isn't billed.
Company tools bill like their v3 endpoints. A match with results, and a company record found, cost 1 credit each. Relationships cost 1 credit per company per billing period, across both sides and every page.
Contact info is charged from your contact balance, per channel found, as the contact info API is.
Warning: each discover_count returns a new queryId, and reading each one bills 1 credit, even with the same filters. Counting is free, but an expired queryId means counting again, and reading that one bills again.
How the tools change#
We change a tool in place only when existing callers keep working. Adding an optional argument or improving a description ships straight away.
A breaking change ships as a new tool. The new tool takes a new name and runs beside the old one until nobody calls the old one. The /v1 in the server URL changes only for a change every tool must make at once. Both URLs then stay live until a cutover date we publish in advance.
The people records follow the v3 API. Each person discover_people returns has the same fields as a v3 Discover search result, and changes only when that does.