Connect Your MCP Server
If your product exposes an MCP (Model Context Protocol) server, you can plug it into Aimdoc. Your server's tools become available to your agent in chat and to services running in the background — a signed-in user can ask "show me my open invoices" and the agent answers with live data from your product, with no extra login and no per-user OAuth setup.
This is also a distribution channel for your MCP server: every user of your product gets its capabilities through the in-app assistant they already use, whether or not they've ever connected an AI client themselves.
This page is about connecting your MCP server to your Aimdoc agent. To connect Claude or another AI assistant to Aimdoc's authenticated MCP server, see MCP Server. To let visiting AI agents converse with your published agent over a public MCP URL, see Agent Gateway.
How it works
- Your app identifies the signed-in user to Aimdoc via the SDK's
identifycall, signed with your identity-verification secret — the setup described in Identity Verification. - You connect your MCP server once for the organization under Integrations → MCP Servers, with the Aimdoc identity auth mode.
- You bind that server to an agent (agent builder → MCP Servers) and/or a service, and pick which tools each may use.
- When the agent or service calls one of your tools, Aimdoc mints a
short-lived signed token — an identity assertion — naming the current
user, and sends it as the
Authorization: Bearerheader on the MCP request. - Your MCP server verifies the token with the same shared secret and decides what that user may access.
The trust chain is identity verification run in reverse: your server signs identity to Aimdoc at identify time; Aimdoc signs identity back to you at tool-call time. No token is ever exposed to the browser, and you store no per-user auth state. Credentials live on the organization server, not on each agent or service.
Server requirements
- Transport: MCP Streamable HTTP (the current MCP spec transport).
- Reachability: a public HTTPS URL. Private hosts and raw IP addresses are rejected.
- Discovery:
initializeandtools/listmust succeed with any valid Aimdoc-signed token, including anonymous ones — the agent builder's Test connection button and unidentified visitors both present tokens with no user in them. Gate user-scoped tools, not the handshake.
Connect a server
Connect the server once. Every agent and service can then bind to it.
- Open Integrations → MCP Servers → Add MCP Server. You can also connect a server in place from an agent's MCP Servers tab or a service's Tools section.
- Enter a name and your server URL (e.g.
https://mcp.yourcompany.com/mcp). - Choose an authentication mode:
- None — no credential. Only for public servers.
- API key — a static key sent as a Bearer token on every call. Simple, but every request acts as the same principal regardless of who is chatting or which service is running.
- Aimdoc identity — per-user signed tokens, described below. Recommended when your tools return user- or account-specific data.
- Click Test connection. Aimdoc connects, lists your tools, and saves the catalog on the organization server.
Renaming the server, replacing the key, re-discovering tools, disabling, or deleting happens here and reaches every agent and service bound to it. Disable or delete warns with the list of bound consumers first.
The tool names and descriptions you expose are shown to the model, so write them the way you'd write them for any MCP client: say what the tool does and when to call it.
Use it on an agent or service
Agent: open the agent → MCP Servers → Add MCP Server. Pick a connected organization server (or connect a new one), choose a slug, and tick the tools this agent may use. Publish the agent.
Service: in the service editor under Tools, bind the same way. The binding is part of the draft; publish the service for it to reach runs. Start from agent… copies that agent's bindings into the draft without replacing servers you already added.
Using Skills? Enabled MCP tools stay available by default, even in skills mode. If you add an MCP tool to a skill (under MCP server tools), it becomes skill-gated: the agent can call it only while a skill that grants it is active. Use that when a tool should run only in a specific situation — for example, pairing a product MCP tool with booking or escalation instructions. Agents not using skills expose all enabled MCP tools all the time.
Verifying identity assertions
With the Aimdoc identity mode, every request to your MCP server carries a
JWT as a Bearer token, signed with HS256 using your organization's
identity-verification secret — the same secret your server already uses to
sign identify calls (Integrations → Identity Verification). Verification is
a few lines with any JWT library.
Claims
{
"iss": "aimdoc",
"aud": "https://mcp.yourcompany.com/mcp",
"sub": "user_123",
"email": "jane@acme.com",
"account": { "external_id": "account_456", "domain": "acme.com", "name": "Acme" },
"identity_tier": "verified",
"org": "b1f0…",
"agent_id": "9c2e…",
"conversation_id": "77aa…",
"iat": 1754400000,
"exp": 1754400300,
"jti": "f3a1c9d2e8b04477"
}
iss— always"aimdoc".aud— your MCP server URL exactly as configured under Integrations → MCP Servers. Reject tokens whose audience isn't your URL.sub— your own user ID: theexternal_idyour app passed toidentify, echoed back. Present whenidentity_tieris"verified"or"email_reply".email— the user's email, trimmed and lowercased. Only present alongsidesub.account— the account fromidentify(external_id,domain,name; absent fields omitted). Only present when identity claims are present and the user is linked to an account.identity_tier— how Aimdoc authenticated the subject:"verified"— this session proved its identity with a validuser_hash"email_reply"— the user replied from a verified contact's mailbox to a conversation Aimdoc initiated, authenticated by the thread's secret reply token. It carriessub/email/accountlike"verified". If your server only trusts fully signed sessions, keep checkingidentity_tier == "verified"and treat unknown tiers as anonymous."anonymous"— the request genuinely comes from Aimdoc, but the visitor is not identified
org,agent_id,conversation_id— Aimdoc-side context IDs, useful for logging and support.conversation_idis present on live chats.iat/exp— tokens live for about 5 minutes and are minted per call. Nothing to store or refresh on your side.
The JWT header carries "typ": "aimdoc-identity-assertion+jwt" if you want
to route on it.
What to check
- The signature is valid HS256 under your Aimdoc signing secret. Pin the algorithm to HS256 — never let the token header choose it.
exphas not passed (standard libraries check this by default).audequals your configured server URL.issequals"aimdoc".
Then authorize: map sub to a user in your system (it's your ID — no lookup
table needed) and scope tool results to what that user may see. Treat
identity_tier: "anonymous" like an unauthenticated caller: allow the
handshake and any public tools, and have user-scoped tools return an error
such as "sign in to your account to use this".
If your server only trusts fully signed sessions, keep checking
identity_tier == "verified" and treat unknown tiers — including
email_reply — as anonymous. Accept email_reply only when mailbox
possession is enough trust for the tools you expose.
// Node.js (Express-style middleware). The secret must stay server-side —
// same rule as identity verification.
import jwt from 'jsonwebtoken'
const AIMDOC_SECRET = process.env.AIMDOC_IDENTITY_SECRET
const MCP_URL = 'https://mcp.yourcompany.com/mcp'
function verifyAimdocToken(req, res, next) {
const token = (req.headers.authorization || '').replace(/^Bearer /, '')
try {
const claims = jwt.verify(token, AIMDOC_SECRET, {
algorithms: ['HS256'],
audience: MCP_URL,
issuer: 'aimdoc',
})
// Fail closed: only trust fully signed sessions. Accept "email_reply"
// here too if mailbox-possession trust is enough for your tools.
const trusted = claims.identity_tier === 'verified'
req.aimdoc = {
userId: trusted ? claims.sub : null,
accountId: claims.account?.external_id ?? null,
verified: trusted,
}
next()
} catch {
res.status(401).json({ error: 'invalid_token' })
}
}
# Python (FastAPI-style dependency)
import os
import jwt # PyJWT
from fastapi import HTTPException, Request
AIMDOC_SECRET = os.environ["AIMDOC_IDENTITY_SECRET"]
MCP_URL = "https://mcp.yourcompany.com/mcp"
def verify_aimdoc_token(request: Request) -> dict:
token = (request.headers.get("authorization") or "").removeprefix("Bearer ")
try:
claims = jwt.decode(
token,
AIMDOC_SECRET,
algorithms=["HS256"], # pin the algorithm
audience=MCP_URL,
issuer="aimdoc",
options={"require": ["exp", "iat", "aud", "iss"]},
)
except jwt.InvalidTokenError:
raise HTTPException(status_code=401, detail="invalid_token")
# Fail closed: only trust fully signed sessions. Accept "email_reply"
# here too if mailbox-possession trust is enough for your tools.
trusted = claims.get("identity_tier") == "verified"
return {
"user_id": claims.get("sub") if trusted else None,
"account_id": (claims.get("account") or {}).get("external_id"),
"verified": trusted,
}
Assertions from background service runs
In addition to live conversations, Aimdoc can call your MCP server from a
background service run — an automated task an agent performs on
a user's behalf (for example, a weekly usage report the user asked for).
These requests carry the same signed assertion (HS256, your signing secret,
about 5 minutes, aud = your MCP server URL), with three additional claims:
{
"context": "service_run",
"service": {
"key": "weekly_usage_report",
"service_id": "…",
"version_id": "…",
"automation_id": "…",
"run_id": "…"
},
"grant": {
"captured_at": "2026-08-15T20:09:00Z",
"created_via": "agent"
}
}
context: "service_run"marks the request as delegated: no user is present in a live session; Aimdoc is acting under an authorization the user (or a workspace admin) granted earlier. Requests without this claim are live-conversation calls, unchanged from before.serviceidentifies what is running:keyis the stable service identifier,run_idthe individual execution.version_idandautomation_idare present when they apply. If you want per-service authorization, allowlist onservice.key.grantsays when and how the delegation was captured (created_viais"agent","admin","sdk", or"api").grantis only present on"verified"identity.- Identity claims (
sub,email,account) are present when the run is for a verified user, andsubis theexternal_idyour ownidentifycall supplied. Anonymous-tier service runs carry no identity orgrantclaims — treat them like anonymous visitors.
You do not need to change anything for these requests to work: signature,
aud, exp, and tier checks are identical. Validate the new claims only if
you want to distinguish delegated from live traffic, authorize per service,
or log run ids. If your validator rejects unknown claims, allow these three.
Operational notes
- Rotating your secret (Integrations → Identity Verification → rotate)
immediately invalidates both directions: your
identifysignatures and Aimdoc's assertions to your server. Update your server's copy of the secret at the same time. In-flight assertions expire within minutes. - Per-call tokens: Aimdoc mints a fresh assertion for every tool call. Your server needs no session state, token cache, or refresh logic.
- Limits: the agent makes at most 5 MCP tool calls per assistant turn; connection and call timeouts are 10 and 30 seconds; tool results are truncated at 20,000 characters. Return compact, structured results — the model reads them directly.
- Changing your server's tools: the catalog is captured when you last tested or re-discovered the organization server. After adding or changing tools, open Integrations → MCP Servers, re-discover, enable the new tools on each agent or service that should use them, and publish.
FAQ
Why don't my users have to log in to use these tools?
They already did — in your app. Your app vouches for their identity via the
signed identify call, and Aimdoc relays that identity to your MCP server in
a token you can verify. There is no third login because there is no third
party: both ends of the chain already trust your signing secret.
What do anonymous visitors get?
Chats where the visitor hasn't been identified (or the session's user_hash
didn't verify) send assertions with identity_tier: "anonymous" and no
sub. Your server decides: expose public tools, or return an error the agent
will relay gracefully. Authenticated replies on agent-initiated email threads
may instead arrive as email_reply with identity claims — see
Verifying identity assertions.
Can I use this token format with other clients? The claims are shaped to align with the OAuth Identity Assertion Authorization Grant (the standard behind MCP's Enterprise-Managed Authorization), so a future migration to standards-based cross-app access is a claims-mapping exercise, not a redesign.
Is the shared secret safe enough? The secret already protects identity verification, and it only grants access to your own server. Keep it server-side on both ends. If you need asymmetric keys, contact us — it's on the roadmap.