Connect Your MCP Server
If your product exposes an MCP (Model Context Protocol) server, you can plug it into your Aimdoc agent. Your server's tools become available to the agent in chat — 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 MCP server, see MCP Server.
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 add your MCP server's URL in the agent builder under MCP Servers and pick the Aimdoc identity auth mode.
- When the agent 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.
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.
Setup in the agent builder
- Open your agent → MCP Servers → Add MCP Server.
- 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.
- 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 shows them with checkboxes — untick any tool the agent shouldn't use.
- Save, then publish the agent.
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.
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 in the agent builder. Reject tokens whose audience isn't your URL.sub— your own user ID: theexternal_idyour app passed toidentify, echoed back. Only present whenidentity_tierisverified.email— the user's email, trimmed and lowercased. Only present alongsidesub.account— the account fromidentify(external_id,domain,name; absent fields omitted). Only present for verified users linked to an account.identity_tier—"verified"(this session proved its identity with a validuser_hash) or"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.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".
// 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',
})
// claims.sub is YOUR user id when identity_tier === 'verified'
req.aimdoc = {
userId: claims.identity_tier === 'verified' ? claims.sub : null,
accountId: claims.account?.external_id ?? null,
verified: claims.identity_tier === 'verified',
}
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")
return {
"user_id": claims.get("sub") if claims.get("identity_tier") == "verified" else None,
"account_id": (claims.get("account") or {}).get("external_id"),
"verified": claims.get("identity_tier") == "verified",
}
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 agent uses the tool schemas captured when you last clicked Test connection. After adding or changing tools, re-test the connection in the agent builder and re-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.
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.