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.

How it works

  1. Your app identifies the signed-in user to Aimdoc via the SDK's identify call, signed with your identity-verification secret — the setup described in Identity Verification.
  2. You add your MCP server's URL in the agent builder under MCP Servers and pick the Aimdoc identity auth mode.
  3. 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: Bearer header on the MCP request.
  4. 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: initialize and tools/list must 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

  1. Open your agent → MCP ServersAdd MCP Server.
  2. Enter a name and your server URL (e.g. https://mcp.yourcompany.com/mcp).
  3. 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.
  4. Click Test connection. Aimdoc connects, lists your tools, and shows them with checkboxes — untick any tool the agent shouldn't use.
  5. 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.

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.
  • subyour own user ID: the external_id your app passed to identify, echoed back. Only present when identity_tier is verified.
  • email — the user's email, trimmed and lowercased. Only present alongside sub.
  • account — the account from identify (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 valid user_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

  1. The signature is valid HS256 under your Aimdoc signing secret. Pin the algorithm to HS256 — never let the token header choose it.
  2. exp has not passed (standard libraries check this by default).
  3. aud equals your configured server URL.
  4. iss equals "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 identify signatures 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.

Was this page helpful?