Skip to main content

Conciergent

CI Python Version License: MIT

Give your MCP tools a chat face. Connect Conciergent to any Model Context Protocol server and it becomes a Slack or LINE bot that can actually do things, with per-user OAuth handled inside the conversation, an approval gate before destructive tools run, and one structured reply that renders natively on every surface.

Conciergent pairs with its sister project openapi-mcp-gateway, which turns any REST API into MCP tools. Together they take one or more OpenAPI specs all the way to a chatbot your users can talk to.

Conciergent architecture, layered top to bottom. Chat surfaces (Slack, LINE, and more) sit on top. An incoming message flows down into a surface- and agent-agnostic runtime that produces one structured reply (plain text, a Card, or a Carousel). The runtime hands each turn to an AI agent powered by Pydantic AI, which resolves to a normal reply, an in-chat OAuth authorization, or a human-in-the-loop confirmation. The agent calls MCP tools (an OpenAPI spec via the embedded openapi-mcp-gateway, or any MCP server) and stores messages in Redis and credentials in Postgres. The reply flows back up to each surface.

  • Any MCP Server, or an OpenAPI Spec Directly. Point Conciergent at an MCP URL, or set gateway.enabled and drop in a spec. It embeds openapi-mcp-gateway in-process, no second server to run.
  • In-Chat OAuth. When a tool needs authorization, Conciergent shows the link in the chat, then stores and refreshes the token. The user never leaves the conversation.
  • Human-in-the-Loop. Any tool the server marks destructive pauses behind a Confirm / Cancel card before it runs.
  • Surface-Agnostic Rich Replies. The agent emits one structured reply, and each surface renders it natively.

Quick Start

Two things are yours to set up once. Register a chat app with a public webhook URL, and have a Redis and a Postgres to run against (the Docker path below provides both). Everything else is ${ENV_VAR} in one YAML file.

1. Install and Scaffold

uv add conciergent
uv run conciergent init

uv run conciergent init writes an annotated conciergent.yml, deep-merged over the shipped defaults so you set only what you change.

2. Configure Your MCP Tools

Conciergent reaches your tools two ways, and you can use both at once.

Connect an MCP Server

List any MCP server URL under agent.mcp_servers. The scaffolded conciergent.yml already has the surface and store set up around it.

# conciergent.yml
agent:
  model: openai:gpt-4o-mini
  system_prompt: |
    You are a helpful assistant. Use your tools to answer the user's requests.
  mcp_servers:
    - http://localhost:9000/mcp
    - https://another-server.example.com/mcp

If a server uses OAuth, Conciergent runs the in-chat authorization handoff the first time a tool needs it, with no extra config.

Or Embed an OpenAPI Spec

Add the gateway extra and let Conciergent embed openapi-mcp-gateway in-process, so a spec becomes MCP tools with no second server to run.

uv add "conciergent[gateway]"
gateway:
  enabled: true
  specs:
    - name: petstore
      spec: https://petstore3.swagger.io/api/v3/openapi.json
      base_url: https://petstore3.swagger.io/api/v3
    - name: internal
      spec: ./internal-api.json

Each spec is served at /{name}/mcp and wired into the agent for you, alongside anything already in agent.mcp_servers. A complete runnable config lives at examples/openapi-chat.yml.

A spec entry mirrors openapi-mcp-gateway's per-server config, so you can add exposure: dynamic for a large spec (the agent sees three meta-tools instead of one per endpoint), a policy filter, or auth (bearer, api_key, or oauth2). An oauth2 spec runs the same in-chat OAuth handoff, so each user authorizes their own account before its tools run.

3. Connect Your Chat App

Conciergent replies in direct messages, and the in-chat OAuth happens there too. Register the app once and set its request URLs, where {your-public-url} is your public host.

Slack

Create the app from examples/slack-app-manifest.yml, which fills these in for you.

Setting URL
Event Subscriptions Request URL https://{your-public-url}/slack/events
Interactivity Request URL https://{your-public-url}/slack/interactions
OAuth Redirect URL (multi-workspace install only) https://{your-public-url}/oauth/slack/callback
LINE

In the LINE Developers console:

  1. Create a provider, then a Messaging API channel under it.
  2. Copy the Channel secret (Basic settings) and issue a long-lived Channel access token (Messaging API tab) into LINE_CHANNEL_SECRET and LINE_CHANNEL_ACCESS_TOKEN.
  3. Set the webhook URL and turn Use webhook on:
    • Messaging API Webhook URL: https://{your-public-url}/line/events
  4. In the LINE Official Account Manager, turn auto-reply and greeting messages off, so the bot owns every reply.

Conciergent answers with the event's one-time reply token when it can and falls back to a push message otherwise, so the channel access token needs push messages enabled.

The MCP OAuth return (/oauth/mcp/callback) is registered with the MCP server automatically, so it is not something you set in a dashboard. For local development, run a tunnel (cloudflared / ngrok) in front of the port and use its URL as {your-public-url} and as server.url.

4. Run

Two ways, depending on whether you already have Redis and Postgres.

Against your own Redis and Postgres:

createdb conciergent      # the database must exist, and Conciergent will create its tables on first run
uv run conciergent run

Or with Docker, which brings up Redis, Postgres, and the app together and needs no uv:

cp examples/openapi-chat.yml conciergent.yml     # or use your own
docker compose up

Secrets stay in the environment. conciergent.yml reads the Slack, LINE, and provider credentials through ${...}, so nothing sensitive is committed. The app serves on port 8000, so put your tunnel in front of it and set server.url to the tunnel URL.

Configuration

Conciergent reads one conciergent.yml, merged over the shipped defaults, so you set only what you change. ${VAR} / ${VAR:-default} resolve in any string field.

Three model providers ship in the box. Set agent.model to a provider:model string and export that provider's API key.

Provider agent.model API key
OpenAI openai:<model> OPENAI_API_KEY
Google Gemini google:<model> GOOGLE_API_KEY
Anthropic Claude anthropic:<model> ANTHROPIC_API_KEY

The shipped default is openai:gpt-4o-mini. Any model the provider offers works, so pick the current one from its docs.

Config Reference
Field Default Description
server.host 127.0.0.1 Bind address. Use 0.0.0.0 to accept connections from other hosts.
server.port 8000 Bind port.
server.url (from host/port) Public URL external services reach. Set this behind a tunnel or proxy.
agent.model openai:gpt-4o-mini A provider:model string for one of the three providers above.
agent.system_prompt (generic assistant) Your assistant's instructions.
agent.mcp_servers [] MCP server URLs the agent connects to.
agent.input_token_limit null Overrides the context window used for history compaction. Unset auto-detects it per model.
agent.mcp_read_timeout_seconds 300 Per-call MCP read timeout. Must exceed conversation.oauth_wait_timeout_seconds, since a missing token runs OAuth inside the connect.
agent.client_name conciergent Name shown on the MCP OAuth screen.
surface.slack.enabled false Turn the Slack surface on.
surface.slack.signing_secret (required if enabled) Verifies inbound Slack signatures.
surface.slack.bot_token (empty) Single-workspace bot token.
surface.slack.client_id · client_secret (empty) Set both for the multi-workspace install flow.
surface.slack.brand_color · destructive_color #586af2 · #DC3545 Card accent colors.
surface.line.enabled false Turn the LINE surface on.
surface.line.channel_secret · channel_access_token (required if enabled) LINE Messaging API credentials.
store.messages_url (required) Redis URL. Holds message state that expires (history, approvals, dedupe, OAuth handoff).
store.credentials_url (required) Postgres URL (any SQLAlchemy async engine). Holds credentials that survive a restart (MCP and bot tokens).
store.max_turns 10 Recent turns kept in history.
gateway.enabled false Embed openapi-mcp-gateway in-process.
gateway.specs [] {name, spec, base_url} entries, each mounted at /{name}/mcp.
conversation.approval_ttl_seconds 600 How long a pending approval waits.
conversation.history_ttl_seconds 604800 History retention (one week).
conversation.oauth_wait_timeout_seconds 240 How long an in-chat OAuth handoff blocks.
logger.level · format · file INFO · text · (none) Logging. format is text or json.
locales_dir null Directory of {lang}.yml files overriding shipped UI text.

Localizing Text

Button labels, prompts, and greetings are not config. They live in a locale catalog, picked from each user's Slack or LINE language. Set locales_dir to a directory of {lang}.yml files to rebrand or translate. examples/locales/en.yml is the full English catalog to start from.

The Reply Model

The agent never speaks Slack or LINE. It emits one of three shapes, and each surface renders it natively.

  • str for plain text.
  • Card for a header, up to six text sections, an optional hero image, a footnote, up to five link buttons, and up to three suggestion quick-replies.
  • Carousel for one to four option cards the user picks between, plus a fallback card.

A suggestion is the interactive primitive. Tapping one posts its prompt back to the agent as if the user had typed it. The field descriptions on these models are the agent's structured-output schema, so the model fills them in directly.

Extending

The paved road above needs no code. These are for teams who want to go further.

Add a Surface

A surface is one platform's whole contribution, behind a one-method contract. The app only ever speaks this interface, so a new platform is a new implementation passed to the app and nothing in the core changes.

class Surface(abc.ABC):
    @abc.abstractmethod
    def build_routers(self, context: SurfaceContext) -> list[fastapi.APIRouter]:
        """Return the webhook and auxiliary routes this platform needs."""

Implement Surface, a ReplySurface to render the reply model, and (if it has per-user auth) an OAuthBridge, then pass an instance to the app. Slack and LINE are just the two that ship.

Use the Python API

App.from_config is the YAML path. For full control, assemble App directly, which is the same object the CLI builds. App.build_asgi() returns the FastAPI app if you would rather bring your own server.

from conciergent import App, MessageStore, CredentialStore
from conciergent.agent.runner import ChatRunner
from conciergent.surfaces.slack.app import Slack

message_store = MessageStore.from_url('redis://localhost:6379/0')
credential_store = CredentialStore.from_url('postgresql+asyncpg://localhost/conciergent')

app = App(
    runner=ChatRunner(
        model='openai:gpt-4o-mini',
        system_prompt='You are a helpful assistant.',
        mcp_servers=['http://localhost:9000/mcp'],
        credential_store=credential_store,
        redirect_uri='https://your-public-url/oauth/mcp/callback',
    ),
    surfaces=[Slack(signing_secret='...', bot_token='xoxb-...')],
    message_store=message_store,
    credential_store=credential_store,
    base_url='https://your-public-url',
)
app.run()

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

conciergent-0.1.0.tar.gz (399.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

conciergent-0.1.0-py3-none-any.whl (65.0 kB view details)

Uploaded Python 3

File details

Details for the file conciergent-0.1.0.tar.gz.

File metadata

  • Download URL: conciergent-0.1.0.tar.gz
  • Upload date:
  • Size: 399.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for conciergent-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0422714b89367344a00a9592e22d6874c48db386ee9ca1da74d65b88350b96c9
MD5 5a150a314ad36a123602149de9a4fc3f
BLAKE2b-256 7aeec0c0665e4110303812314d49c8e1fbffdd3fcc710abdd4e040433344f917

See more details on using hashes here.

Provenance

The following attestation bundles were made for conciergent-0.1.0.tar.gz:

Publisher: release.yml on mroops0111/conciergent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file conciergent-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: conciergent-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 65.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for conciergent-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6ec9e1c1e24624b4eb2b5373d00e3313714eac199265e9d6564d3bcab72dfba5
MD5 e516ece3c93b161baafea97d7d145d14
BLAKE2b-256 48da6b5785b92065b2b78ce7fe9d679f73eab09555f7560da18eec5acf91a46c

See more details on using hashes here.

Provenance

The following attestation bundles were made for conciergent-0.1.0-py3-none-any.whl:

Publisher: release.yml on mroops0111/conciergent

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page