Install
$ agentstack add mcp-mroops0111-conciergent ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Conciergent
[](https://github.com/mroops0111/conciergent/actions/workflows/ci.yml) [](https://www.python.org/downloads/) [](LICENSE)
Give your MCP tools a chat face. Connect Conciergent to any Model Context Protocol server and it becomes a Slack, LINE, or Discord 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.
- Any MCP Server, or an OpenAPI Spec Directly. Point Conciergent at an MCP URL, or set
gateway.enabledand 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, and can ask to sign out at any time to revoke it.
- 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](#4-run) 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 manifest.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 manifest.yml already has the surface and store set up around it.
# manifest.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](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/manifest-slack.yml](examples/manifest-slack.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:
- Create a provider, then a Messaging API channel under it.
- Copy the Channel secret (Basic settings) and issue a long-lived Channel access token (Messaging API tab) into
LINE_CHANNEL_SECRETandLINE_CHANNEL_ACCESS_TOKEN. - Set the webhook URL and turn Use webhook on:
- Messaging API Webhook URL:
https://{your-public-url}/line/events
- 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.
Discord
In the Discord Developer Portal:
- Create an application, add a Bot, and copy its token into
DISCORD_BOT_TOKEN. - Enable the bot's Direct Messages intent. It is non-privileged, and direct-message content is exempt from the message-content intent.
- Invite the bot to a server you also belong to, so it can open a direct-message channel with you.
Discord delivers direct messages over a gateway connection the bot holds open, so there is no webhook URL to register. It connects on startup and replies in direct messages.
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 manifest.yml # or use your own
docker compose up
Secrets stay in the environment. manifest.yml reads the Slack, LINE, Discord, 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 manifest.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: | OPENAI_API_KEY | | Google Gemini | google: | GOOGLE_API_KEY | | Anthropic Claude | anthropic: | 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. | | surface.discord.enabled | false | Turn the Discord surface on. | | surface.discord.bot_token | (required if enabled) | Discord bot token. The bot connects over the gateway, so no webhook URL is needed. | | 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, LINE, or Discord language. Set locales_dir to a directory of {lang}.yml files to rebrand or translate. [examples/locales/en.yml](examples/locales/en.yml) is the full English catalog to start from.
The Reply Model
The agent never speaks Slack, LINE, or Discord. It emits one of three shapes, and each surface renders it natively.
strfor plain text.Cardfor 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.Carouselfor 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, LINE, and Discord are the three 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](LICENSE)
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: mroops0111
- Source: mroops0111/conciergent
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.