# uare.ai Experts MCP server

> A public MCP server with no key: an assistant such as ChatGPT or Claude finds a listed uare.ai expert for a person's need, and the person talks to that expert's AI as a visitor.

The uare.ai Experts MCP server brings experts' AIs into the assistants people already use. An assistant such as ChatGPT or Claude finds a listed expert for what a person needs, the person talks to that expert's AI as a visitor without leaving their conversation, and they can continue with the expert on uare.ai.

```text
https://app.uare.ai/api/v1/public/mcp
```

It's a public, remote MCP server over the streamable HTTP transport. It needs no API key and no sign-in, and it reaches only what any visitor can see: listed experts' public profiles, and the answers their AIs give in public mode.

> [!NOTE]
> This isn't [your MCP server](/docs/mcp/quickstart), which takes your API key, acts as you and reaches your private context. [Two MCP servers](/docs/concepts#two-mcp-servers) compares them.

## How it works

1. A person asks their assistant: "I'm running my first marathon in April. Who could help me plan my training?"
2. The assistant calls [`find_experts`](#find_experts) with that need. uare.ai searches the experts it lists and returns up to three, each with a one-line reason, or says that none matches.
3. The person picks Ana, a running coach. The assistant calls [`ask_expert`](#ask_expert) with their message, and Ana's AI answers as it would a visitor on her public profile. The reply is labelled as her AI's: "The AI of Ana replied".
4. With the next message, the assistant passes back the `consult` value from that reply, so Ana's AI remembers the conversation. See [Keep a conversation going](#keep-a-conversation-going).
5. [`expert_profile`](#expert_profile) shows more about Ana: her bio, her public services and a link to her page. When the conversation's free messages run out, the reply sends the person to that page.

## What experts should know

- **You choose to be listed.** Only listed experts appear: `find_experts` searches only them, and `ask_expert` and `expert_profile` answer only for them. For now, uare.ai lists experts who agree to it. Once the uare.ai Experts app is in ChatGPT's and Claude's directories, a setting in your AI's settings (Settings → ChatGPT & Claude) lets you list your AI yourself, and take it down at once. Only a personal AI can be listed, not an organization's, and places are limited for now. uare.ai can take a listing down and keep it down.
- **Your AI must be public.** A listed expert appears only while their AI is public. Make your AI private and it stops appearing and answering there at once.
- **You must be verified and 18 or older on record.** The same rule uare.ai's own search uses: a listed expert appears only while their identity is verified and their account shows them as 18 or older, from the birthday on their profile or the date of birth their identity check confirmed. Without an age on record they don't appear, even when listed.
- **It answers as on your public profile.** Your AI answers in public mode, from the knowledge you've made public, without your private notes or connected apps. Replies are labelled as your AI's, never as you in person.
- **You see the conversations.** Conversations through these assistants are visitor conversations: they show with your AI's other guest conversations, beside chats from your public profile and your website. Like any guest, the person is anonymous.
- **Prices stay on uare.ai.** `expert_profile` lists your public services by name, category and description, never with a price. In other assistants it can add whether a service is free or for subscribers; inside ChatGPT it leaves that out too. People see your prices on your page.
- **People continue on your page.** Results link to your public profile. When a conversation's free messages run out, or a daily limit is reached, the reply sends the person there to keep talking to your AI.

## Connect it for testing

To try the server, add it to an assistant yourself, as a custom MCP server with no sign-in. Assistants may ask before each `ask_expert` call, because it sends the person's message to uare.ai. `find_experts` and `expert_profile` are marked read-only.

### Claude

In Claude on the web, Claude Desktop or Cowork, open **Customize → Connectors**, select **+ Add**, then **Add custom connector**. Name it, for example *uare.ai Experts*, paste the URL above, choose **No sign in**, then select **Add**. Turn it on in a conversation from the **+** menu, under **Connectors**.

On Team and Enterprise plans, an Owner adds the connector first, in **Organization settings → Connectors**. Anthropic's guide, [Get started with custom connectors using remote MCP](https://support.claude.com/en/articles/11175166-get-started-with-custom-connectors-using-remote-mcp), has the current steps.

In Claude Code, one command adds it:

```bash
claude mcp add --transport http uare-experts \
  https://app.uare.ai/api/v1/public/mcp
```

### ChatGPT

In ChatGPT on the web, open **Plugins**, select the plus button, then **Add custom MCP server**. Enter a name, paste the URL above as the **Server URL**, choose **No authentication**, accept the risk warning and select **Create as a plugin**. Install the plugin from your personal plugins, then type `@` in a conversation and pick it.

Workspace settings can restrict custom MCP servers. OpenAI's guide, [Add custom MCP server](https://developers.openai.com/api/docs/guides/custom-mcp-server), has the current steps.

### Your own code

Any MCP client can use the server. In Node.js, with the official SDK:

```bash
npm install @modelcontextprotocol/sdk
```

```js title="ask-an-expert.mjs"
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const client = new Client({ name: "my-assistant", version: "1.0.0" });
await client.connect(
  new StreamableHTTPClientTransport(new URL("https://app.uare.ai/api/v1/public/mcp")),
);

const found = await client.callTool({
  name: "find_experts",
  arguments: { need: "I am running my first marathon in April and need a training plan" },
});
const [expert] = found.structuredContent?.experts ?? [];

if (!expert) {
  // No listed expert is close enough: the text says so.
  console.log(found.content[0].text);
} else {
  const ask = (message, consult) =>
    client.callTool({
      name: "ask_expert",
      arguments: { expert_id: expert.expert_id, message, ...(consult && { consult }) },
    });

  const first = await ask("I run 20 km a week. Can I be ready for a marathon in April?");
  console.log(first.content[0].text);

  // Pass the consult back to stay in the same conversation.
  const second = await ask("What should my long runs look like?", first.structuredContent?.consult);
  console.log(second.content[0].text);
}

await client.close();
```

The server keeps no session, so a single request can call a tool, with no `initialize` first:

```bash
curl -s https://app.uare.ai/api/v1/public/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"find_experts","arguments":{"need":"I am running my first marathon in April and need a training plan"}}}'
```

It answers with an `event: message` line whose data holds the JSON-RPC result.

## Tools

| Tool | Does | Behavior |
| --- | --- | --- |
| [`find_experts`](#find_experts) | Finds up to three listed experts for a need, each with a one-line reason | Read-only |
| [`ask_expert`](#ask_expert) | Sends a message to an expert's AI and returns its reply | Sends the message to uare.ai |
| [`expert_profile`](#expert_profile) | Returns an expert's profile, their services and a link to their page | Read-only |

Each result has a text block written for the assistant's model, and the same data as JSON in `structuredContent` for your own code. An error is a result with `isError: true` and a message to show the person: see [Errors](#errors).

## find_experts

Finds up to three listed experts whose AI can help with a need, best match first. uare.ai compares the need with each listed expert's public profile and keeps only close matches. When none is close enough, `experts` is empty and the text says `No uare.ai expert matches this need yet.`

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `need` | string | Yes | What the person needs help with, in their words. Up to 1,000 characters. |

```json
{
  "experts": [
    {
      "expert_id": "08aeb20d-b364-4dd4-a36e-7c5c55887015",
      "name": "Ana",
      "reason": "Story: Ana has coached first-time marathoners for ten years.",
      "profile_url": "https://app.uare.ai/p/08aeb20d-b364-4dd4-a36e-7c5c55887015?utm_source=claude&utm_medium=assistant&utm_campaign=uare_experts_app"
    }
  ]
}
```

| Field | What it is |
| --- | --- |
| `expert_id` | The expert's ID, for `ask_expert` and `expert_profile`. |
| `name` | The expert's name. |
| `reason` | Why they match: one line from the part of their public profile closest to the need. |
| `profile_url` | Their page on uare.ai. `utm_source` names the assistant: `claude` or `chatgpt` when uare.ai recognizes that assistant's servers, `mcp` otherwise. |

## ask_expert

Sends a message to an expert's AI and returns its reply. The expert's AI answers in public mode, as it would a visitor on the expert's public profile.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `expert_id` | string | Yes | The `expert_id` from `find_experts`. |
| `message` | string | Yes | The message for the expert, in the person's words. Up to 4,000 characters. |
| `consult` | string | No | The `consult` value from the previous `ask_expert` reply. Leave it out to start a new conversation. |

The text block gives the model the reply, then the `consult` value with an instruction to pass it back:

```text
The AI of Ana replied:

Yes, if you build up gradually. How many times a week do you run now?

consult: eyJhbGciOiJIUzI1NiJ9…
Pass this consult value with the next ask_expert call to continue this conversation (4 free messages left). More from Ana: https://app.uare.ai/p/08aeb20d-b364-4dd4-a36e-7c5c55887015?utm_source=claude&utm_medium=assistant&utm_campaign=uare_experts_app
```

`structuredContent` holds the same:

```json
{
  "expert": {
    "expert_id": "08aeb20d-b364-4dd4-a36e-7c5c55887015",
    "name": "Ana",
    "avatar_url": "https://…/ana.png"
  },
  "reply": "Yes, if you build up gradually. How many times a week do you run now?",
  "consult": "eyJhbGciOiJIUzI1NiJ9…",
  "new_conversation": false,
  "remaining_free_messages": 4,
  "suggestions": [
    { "label": "Plan my long runs", "prompt": "What should my long runs look like?" }
  ],
  "profile_url": "https://app.uare.ai/p/08aeb20d-b364-4dd4-a36e-7c5c55887015?utm_source=claude&utm_medium=assistant&utm_campaign=uare_experts_app"
}
```

| Field | What it is |
| --- | --- |
| `expert` | The `expert_id`, `name` and `avatar_url` of the expert whose AI replied. |
| `reply` | The reply, often with markdown. |
| `consult` | Pass it back with the next message to stay in this conversation. |
| `new_conversation` | `true` when the `consult` you sent had expired, so this reply starts a new conversation. |
| `remaining_free_messages` | Free messages left in this conversation. |
| `suggestions` | Follow-ups the expert's AI offers: a `label` to show and a `prompt` to send. Can be empty. |
| `profile_url` | The expert's page on uare.ai. |

When a limit is reached, `ask_expert` returns a hand-off instead of a reply: see [Limits](#limits).

## expert_profile

Returns an expert's uare.ai profile, the services they offer and a link to their page.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `expert_id` | string | Yes | The `expert_id` from `find_experts`. |

```json
{
  "expert": {
    "expert_id": "08aeb20d-b364-4dd4-a36e-7c5c55887015",
    "name": "Ana",
    "avatar_url": "https://…/ana.png",
    "bio": "Running coach. I help first-time marathoners train without getting hurt.",
    "profile_url": "https://app.uare.ai/p/08aeb20d-b364-4dd4-a36e-7c5c55887015?utm_source=claude&utm_medium=assistant&utm_campaign=uare_experts_app"
  },
  "services": [
    {
      "name": "Marathon plan review",
      "category": "education",
      "description": "I read your training plan and tell you what to change.",
      "access": "for subscribers"
    },
    { "name": "Ask me about your first 10K", "access": "free" }
  ]
}
```

`services` lists the expert's published public services, featured ones first. Each has a `name` and, when they're set, a `category` and a one-line `description`. `access` says `free` or `for subscribers` when the service is one of those. No price is ever included, and inside ChatGPT `access` is left out too. If the services can't be read, `services` is `null`, the text says so, and the rest of the profile still comes back.

## Keep a conversation going

The server keeps no session between calls. The `consult` value is the conversation: `ask_expert` returns one with every reply, in `structuredContent` and on the `consult:` line of its text, and the assistant passes it back with the next message. Without it, `ask_expert` starts a new conversation.

- **What the expert's AI remembers.** Everything said in one consult, as in a visitor's chat on uare.ai, and nothing from other consults.
- **How long it lasts.** Up to two hours, and it ends sooner after an hour without messages.
- **When it expires.** `ask_expert` doesn't fail. It starts a new conversation with the same expert and says so: its text opens with `Started a new conversation with the AI of Ana: the previous consult had expired.`, `new_conversation` is `true`, and it returns a new `consult`. That conversation counts as a new one in the [daily limits](#limits).
- **One message at a time.** A message sent while the previous reply is still being written gets an error. Wait for the reply, then send.
- **It belongs to one person and one expert.** Keep it with that person's conversation, and treat it as opaque. A value that was changed, a session token from the [public chat API](/docs/api/build-your-own), or another expert's consult gets an error that says why. It never quietly starts a new conversation.

## Limits

Conversations through the server are free, so it keeps to limits. They count per caller: one person, when the assistant identifies its user in a way uare.ai can verify, and otherwise one network address. Servers uare.ai recognizes as an assistant's, such as Anthropic's for Claude, count as one shared address with a larger allowance, because all of that assistant's users come through them. Daily limits reset at midnight UTC.

- **Free messages per conversation.** A consult includes the same free messages as a [visitor's conversation](/docs/concepts#visitor-conversations) on a website. Each reply says how many are left.
- **New conversations per expert.** A caller can start a limited number of new conversations with the same expert each day.
- **Messages per day.** Each caller has a daily share of messages, within a daily budget for the whole server.
- **Requests per minute.** Each caller can send only so many requests a minute, `initialize` and `tools/list` included. Past that, the server answers `429` with a `Retry-After` header.

Only `ask_expert` counts toward the daily limits, so `find_experts` and `expert_profile` keep working. When the free messages or a daily limit stop a message, `ask_expert` doesn't fail: it hands the person over to the expert's page. The result isn't an error, and `structuredContent.handoff` says why, with the link:

| `handoff.reason` | When | The text starts with |
| --- | --- | --- |
| `message_limit` | The conversation's free messages are used. | `This conversation with the AI of Ana has used its free messages.` |
| `consult_limit` | The caller has started its share of new conversations with this expert today. | `The free conversations with the AI of Ana through this assistant are used up for today.` |
| `budget` | The caller's daily share, or the server's daily budget, is used. | `The AI of Ana can't take more conversations through this assistant right now.` |

Each text ends with `Continue with Ana on uare.ai:` and the expert's page, which is also `handoff.url`. Show the person that link: on uare.ai they can keep talking to the expert's AI and sign up.

If uare.ai can't count requests or messages for a moment, it refuses them rather than letting them through uncounted.

## Errors

A tool that fails returns a result with `isError: true` and a message the assistant can show the person:

| Tool | Message | What it means |
| --- | --- | --- |
| `ask_expert`, `expert_profile` | `This expert isn't available.` | The `expert_id` isn't a listed expert's, their AI is no longer public, or they aren't verified and 18 or older on record. |
| `find_experts` | `Describe the need in a few words.` | The `need` was blank. |
| `ask_expert` | `Send a message for the expert.` | The `message` was blank. |
| `ask_expert` | `Couldn't continue that consult: the consult value isn't valid. …` | The value was changed or isn't one uare.ai issued. Call `ask_expert` without `consult` to start a new conversation. |
| `ask_expert` | `Couldn't continue that consult: it wasn't started through this app. …` | It's a session token from the public chat API, not a consult. |
| `ask_expert` | `That consult belongs to expert …` | The consult is another expert's. Use it with that expert, or start a new conversation with this one. |
| `ask_expert` | `The AI of Ana is still answering the previous message in this consult. …` | Wait for that reply, then send the next message. |
| `ask_expert` | `The AI of Ana couldn't answer just now. Try again in a moment.` | The reply failed or came back empty. When the error carries a `consult` value, retry with it to stay in the conversation. |
| `ask_expert` | `The AI of Ana can't take messages here right now. Try again later.` | uare.ai can't count messages right now, so it takes none. |
| `ask_expert` | `The expert can't be reached right now. …` | uare.ai couldn't check the expert's listing. Retry in a moment. |
| `expert_profile` | `The expert's profile can't be read right now. …` | uare.ai couldn't check the expert's listing. Retry in a moment. |
| `find_experts` | `Expert search is unavailable right now. Try again in a moment.` | `find_experts` couldn't search. |
| Any | `Input validation error: Invalid arguments for tool …` | An argument doesn't fit the tool's parameters, and the rest of the message names it: a `message` over 4,000 characters (`Too big: expected string to have <=4000 characters`), a `need` over 1,000, an empty or missing argument, or an `expert_id` that isn't an ID in the form `find_experts` returns. |
| Any | `Something went wrong on uare.ai's side. Try again in a moment.` | An unexpected failure. |

The HTTP request can also be refused before any tool runs:

| Status | What it means |
| --- | --- |
| `429` | Too many requests from this caller. Wait the seconds in `Retry-After`, then retry. |
| `503` | uare.ai can't count requests right now. Retry after `Retry-After`. |
| `405` | The server answers `POST` only. It keeps no session, so it doesn't serve `GET` or `DELETE`. |
| `411` | A `DELETE` without a `Content-Length` header is refused before it reaches the server, with `Content-Length header required for this route`. Sent with `Content-Length: 0`, it gets the `405` above. |
