# Composer sources

A "Connected sources" section for Composer's @ menu, from your MCP connections. A connected source can be mentioned and says "Connected". One that needs signing in says so; with `onSignIn`, choosing it starts the sign-in instead of inserting anything, and without it the option says it needs signing in and can't be chosen. An unavailable source stays in the menu with your reason and can't be chosen. Nothing is hidden because it can't be used. Pass what it returns as Composer's `mentions`, with any others.

- Registry name: `composer-sources` (`@candor/composer-sources`)
- Tier: Pro. Installing it needs a Pro, Premium or Team licence key: the person runs `npx candor-ui key <key>` themselves; never ask for the key in chat or write it to a file.
- Docs: https://candor.themethodic.com/docs/composer-sources

## Install

```bash
npx candor-ui add composer-sources
```

Run `npx candor-ui@latest init` once first (https://candor.themethodic.com/get-started.md). Without the CLI: `npx shadcn@latest add @candor/composer-sources`.
It brings in: chat, locale, tokens.

## Example

```tsx
"use client"

import * as React from "react"
import { Composer } from "@/components/candor/composer"
import { useComposerSources, type ConnectedSource } from "@/components/candor/composer-sources"
import type { EntityRef } from "@/lib/candor/chat"

export default function Example() {
  const [value, setValue] = React.useState("")
  const [refs, setRefs] = React.useState<EntityRef[]>([])
  // As your MCP client reports each connection. A real app starts its sign-in flow in onSignIn.
  const [sources, setSources] = React.useState<ConnectedSource[]>([
    { id: "tracker", name: "Issue tracker", status: "connected", description: "tracker.example.com", entity: { kind: "source", name: "Issue tracker" } },
    { id: "mail", name: "Shared inbox", status: "needs-sign-in" },
    { id: "drive", name: "Team drive", status: { kind: "unavailable", reason: "The drive’s server isn’t answering. Last heard from 20 minutes ago." } },
  ])
  const sourceMentions = useComposerSources(sources, {
    onSignIn: (s) => setSources((all) => all.map((x) => (x.id === s.id ? { ...x, status: "connected", entity: { kind: "source", name: x.name } } : x))),
  })
  return (
    <Composer
      className="max-w-2xl"
      value={value}
      onChange={setValue}
      onSubmit={() => setValue("")}
      references={refs}
      onReferencesChange={setRefs}
      placeholder="Type @ to see the connected sources"
      mentions={[{ id: "p1", group: "People", label: "Dana Ortiz", description: "Support lead" }, ...sourceMentions]}
    />
  )
}
```

## Functions

### useComposerSources

```ts
useComposerSources(sources: ConnectedSource[], options?: { /** Called when the person chooses a source that needs signing in. Without it, such a source can't be chosen and says why. */ onSignIn?: ((source: ConnectedSource) => void) | undefined; /** The heading over the section. Defaults to "Connected sources". */ group?: string | undefined; })
```

## Strings (`composerSources`)

The fixed words, English by default. Override any of them under `composerSources` in `<LocaleProvider strings={{ composerSources: { … } }}>`; the table is exported as `composerSourcesStrings`.

- `group`: Connected sources
- `connected`: Connected
- `signIn`: Sign in
- `needsSignIn`: Needs sign-in
- `signInElsewhere`: Needs signing in again before it can be used
- `unavailable`: Unavailable

## Types

### ConnectedSource

One source the agent can read through, as your MCP client reports its connection.

```ts
{
  /** Your id for the connection. */
  id: string;
  /** The name to show, e.g. "Linear", as your app knows it. */
  name: string;
  /** The connection as it stands: "connected", "needs-sign-in" (missing or expired), or unavailable with the reason in your app's words. */
  status: "connected" | "needs-sign-in" | {
    kind: "unavailable";
    reason: string;
  };
  /** One line under the name while it can be used, such as the server's host. */
  description?: string | undefined;
  /** What @ inserts for it, as a chip the agent receives as a reference. Without one, "@Name" is typed. */
  entity?: EntityRef | undefined;
}
```

## Types from other items

The props above take these. Each is defined by another item; import it from the path shown.

### EntityRef

Import: `import type { EntityRef } from "@/lib/candor/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

```ts
{
  kind: EntityKind;
  /** The identifier, e.g. "orders.status" or "billing.invoice.due_date". */
  name: string;
  /** A friendlier label when there is one, e.g. "Region". */
  label?: string | undefined;
  /** Where it lives, when more than one place is in scope, e.g. "Shop staging". */
  location?: string | undefined;
}
```

### EntityKind

Import: `import type { EntityKind } from "@/lib/candor/chat"` (item `chat`, https://candor.themethodic.com/docs/chat.md)

What kind of thing an entity is: "record", "table", "workflow"… Any string; the app registers each kind's icon and noun (see EntityKindsProvider), and presets cover common systems (e.g. Salesforce).

```ts
string
```

## Rules for using Candor UI

- Components take plain typed data. Never pass model output as HTML or render it as a link; messages are typed blocks.
- A control that can't be used says why: wrap it in `<Locked reason="…">`, don't just disable it.
- Don't show a value you don't have: leave it out (it renders as —) rather than guess. An estimate is labelled as one.
- Nothing is pre-selected in a decision a person makes, and the agent is named from your app's data, never from model output.
- Your app's words go in `LocaleProvider`: its fixed strings in `strings`, its nouns in `nouns`; entity kinds and agent roles in `EntityKindsProvider` and `AgentRolesProvider`.
