Skip to main content
Glama
rezapars

Client Onboarding MCP

by rezapars

Client Onboarding MCP for Microsoft Copilot and Teams

Minimal Python 3.12 project with:

  • MCP server with client-session authentication and five tools

  • Separate mock backend server

  • REST/OpenAPI facade

  • Microsoft 365 Copilot plugin package files

  • Microsoft Teams app manifest

  • Docker, tests, local setup, auth placeholders, and examples

Architecture:

[Microsoft Teams]
        |
        v
[Microsoft Copilot]
        |
        v
[MCP Server]
        |
        v
[Mock Backend Server]

Folder Tree

.
|-- appPackage/
|   |-- adaptive-cards/
|   |   `-- client-result.json
|   |-- color.png
|   |-- declarativeAgent.json
|   |-- manifest.json
|   |-- mcp-tools.json
|   |-- openapi.yaml
|   |-- outline.png
|   |-- plugin.json
|   `-- plugin.openapi.json
|-- examples/
|   |-- curl.md
|   |-- json-rpc.md
|   |-- requests.http
|   `-- responses/
|       |-- basic_info_family.json
|       |-- basic_info_name.json
|       |-- auth_failed.json
|       |-- auth_required.json
|       |-- auth_success.json
|       |-- error_invalid_field.json
|       |-- facility_limit.json
|       |-- onboarding_status.json
|       `-- outreach_summary.json
|-- mcp_server/
|   |-- __init__.py
|   |-- __main__.py
|   |-- app.py
|   |-- auth.py
|   |-- backend_client.py
|   |-- config.py
|   |-- errors.py
|   |-- jsonrpc.py
|   |-- logging_config.py
|   |-- mcp_app.py
|   |-- models.py
|   |-- routes.py
|   |-- session.py
|   `-- tool_registry.py
|-- mock_backend/
|   |-- __init__.py
|   |-- __main__.py
|   |-- app.py
|   |-- config.py
|   |-- logging_config.py
|   |-- models.py
|   `-- routes.py
|-- tests/
|   |-- conftest.py
|   |-- test_backend.py
|   |-- test_mcp_http.py
|   `-- test_tool_registry.py
|-- .env.example
|-- .vscode/launch.json
|-- Dockerfile
|-- docker-compose.yml
|-- main.py
|-- openapi.yaml
|-- pytest.ini
|-- requirements.txt
`-- tools.json

Related MCP server: mcp-xubio

Tools

The MCP exposes these five tools.

authenticate_client

Input:

{
  "client_id": "123",
  "otp": "9632"
}

Successful output:

{
  "authenticated": true,
  "client_id": "123",
  "message": "Authentication successful"
}

Failed output:

{
  "authenticated": false,
  "message": "Invalid OTP"
}

Business tools use the authenticated client_id stored in the MCP session. For local HTTP tests, keep the same x-mcp-session-id header across calls.

get_client_onboarding_status

Input:

{}

Output:

{
  "client_id": "123",
  "status": "Completed",
  "message": "Client onboarding completed successfully"
}

get_client_basic_info

Input:

{
  "field": "name"
}

Allowed field values:

  • name

  • family

Output:

{
  "client_id": "123",
  "field": "name",
  "value": "John"
}
{
  "client_id": "123",
  "field": "family",
  "value": "Doe"
}

get_client_facility_limit

Input:

{}

Output:

{
  "client_id": "123",
  "client_name": "John Doe",
  "facility_limit_eur": 75000000,
  "currency": "EUR",
  "formatted_limit": "EUR 75,000,000"
}

summarize_client_outreach

Input:

{}

Output includes the outreach reasons, highlights, and total questions to answer:

{
  "client_id": "123",
  "client_name": "John Doe",
  "outreach_count": 2,
  "questions_to_answer_count": 5,
  "reasons": ["Annual facility review", "Updated cash-flow forecast"],
  "summary": "John Doe has 2 outreach items. Reasons: Annual facility review, Updated cash-flow forecast. You should answer 5 questions in total."
}

Local Setup

Use Python 3.12.

python -m venv .venv312
.venv312\Scripts\activate
pip install -r requirements.txt
copy .env.example .env

Terminal 1:

uvicorn mock_backend.app:app --host 0.0.0.0 --port 8001 --reload

Terminal 2:

uvicorn mcp_server.app:app --host 0.0.0.0 --port 8000 --reload

Health checks:

curl http://localhost:8001/health
curl http://localhost:8000/health

REST API

OpenAPI 3.1 schema:

  • Static YAML: openapi.yaml

  • Runtime JSON: http://localhost:8000/openapi.json

  • Runtime YAML file endpoint: http://localhost:8000/openapi.yaml

Endpoints:

curl -X POST http://localhost:8000/api/v1/authenticate \
  -H "Content-Type: application/json" \
  -H "x-mcp-session-id: demo-session" \
  -d '{"client_id":"123","otp":"9632"}'
curl http://localhost:8000/api/v1/client/onboarding-status -H "x-mcp-session-id: demo-session"
curl http://localhost:8000/api/v1/client/basic-info/name -H "x-mcp-session-id: demo-session"
curl http://localhost:8000/api/v1/client/facility -H "x-mcp-session-id: demo-session"
curl http://localhost:8000/api/v1/client/outreach-summary -H "x-mcp-session-id: demo-session"
curl http://localhost:8000/api/v1/clients/123/onboarding-status
curl http://localhost:8000/api/v1/clients/123/basic-info/name
curl http://localhost:8000/api/v1/clients/123/basic-info/family
curl http://localhost:8000/api/v1/clients/123/facility
curl http://localhost:8000/api/v1/clients/123/outreach-summary

MCP JSON-RPC

List tools:

curl -X POST http://localhost:8000/mcp ^
  -H "Content-Type: application/json" ^
  -H "x-mcp-session-id: demo-session" ^
  -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}"

Authenticate first:

curl -X POST http://localhost:8000/mcp ^
  -H "Content-Type: application/json" ^
  -H "x-mcp-session-id: demo-session" ^
  -d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"authenticate_client\",\"arguments\":{\"client_id\":\"123\",\"otp\":\"9632\"}}}"

Then call business tools with the same session ID and no client_id argument:

curl -X POST http://localhost:8000/mcp ^
  -H "Content-Type: application/json" ^
  -H "x-mcp-session-id: demo-session" ^
  -d "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"get_client_onboarding_status\",\"arguments\":{}}}"

The MCP Python SDK registration is in mcp_server/mcp_app.py. For stdio-style MCP testing:

python -m mcp_server.mcp_app

Mock Backend

The mock backend is a separate FastAPI service and returns hardcoded data only.

curl http://localhost:8001/status/123
curl http://localhost:8001/info/123/name
curl http://localhost:8001/info/123/family
curl http://localhost:8001/facility/123
curl http://localhost:8001/outreach/123/summary

MCP Client Session Authentication

MCP client-session authentication is enabled in the tool flow:

  • Tool: authenticate_client

  • Required input: client_id, otp

  • Demo OTP: 9632

  • Storage: in-memory only

  • Session key: x-mcp-session-id or mcp-session-id header for HTTP tests

If a business tool is called before authentication, the MCP tool returns:

{
  "error": "AUTH_REQUIRED",
  "message": "Please authenticate first using client_id and OTP"
}

Production replacement points are marked in session.py:

  • Replace the hardcoded OTP with real OTP validation.

  • Load client/user mappings from a database.

  • Bind session IDs to Microsoft Entra ID or Teams user identity.

  • Store sessions in Redis or another shared session store.

API Key Placeholder

API key validation is intentionally disabled for local development. This is separate from the MCP client-session authentication tool.

Enable API key validation:

MCP_AUTH_REQUIRED=true
MCP_API_KEY_HEADER=x-api-key
MCP_API_KEY=local-dev-key

Then call:

curl http://localhost:8000/api/v1/clients/123/onboarding-status -H "x-api-key: local-dev-key"

Production replacement points:

  • Replace APIKeyAuthMiddleware with Microsoft Entra ID JWT validation.

  • Register an app in Microsoft Entra ID.

  • Configure Teams bot and Copilot plugin auth references with the production app registration.

  • Store secrets in Azure Key Vault or a managed secret store.

Docker

docker compose up --build

Services:

  • MCP server: http://localhost:8000

  • Mock backend: http://localhost:8001

Copilot Package

Package files are in appPackage/.

  • manifest.json: Microsoft 365/Teams app manifest

  • declarativeAgent.json: declarative agent manifest

  • plugin.json: Microsoft 365 Copilot plugin manifest using RemoteMCPServer

  • plugin.openapi.json: OpenAPI fallback plugin manifest

  • mcp-tools.json: MCP tool discovery metadata mirror of the inline plugin metadata

  • openapi.yaml: OpenAPI schema for the REST facade

  • color.png and outline.png: Teams package icons

Before packaging:

  1. Replace the hosted domain in appPackage/plugin.json, appPackage/openapi.yaml, and appPackage/manifest.json if you move away from the current Railway URL.

  2. Replace the app ID in appPackage/manifest.json with a real package ID before production publishing.

  3. Zip the contents of appPackage/, not the folder itself.

  4. Upload the zip in Teams Developer Portal or Microsoft 365 Agents Toolkit.

Example Copilot prompts:

  • Authenticate client 123 with OTP 9632

  • Get my onboarding status

  • What is the client's name?

  • What is the client's family name?

  • What is the current limit on my company's facility?

  • Summarize my outreach highlights

Teams Local Testing

  1. Start both local services.

  2. Start an HTTPS tunnel:

ngrok http 8000
  1. Copy the HTTPS ngrok domain, for example https://abc123.ngrok-free.app.

  2. Replace the hosted domain in appPackage/manifest.json, appPackage/plugin.json, and appPackage/openapi.yaml.

  3. In Teams Developer Portal, import the app package zip.

  4. Validate the manifest and test in a personal scope first.

This project is packaged as a Copilot declarative agent. It does not implement a Bot Framework message handler.

Error Handling Examples

Invalid basic info field through REST:

curl http://localhost:8000/api/v1/clients/123/basic-info/age

Response shape:

{
  "error": {
    "code": "validation_error",
    "message": "Request validation failed.",
    "details": []
  }
}

Invalid MCP tool call:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "get_client_basic_info",
    "arguments": {
      "field": "age"
    }
  }
}

Response shape:

{
  "jsonrpc": "2.0",
  "id": 3,
  "error": {
    "code": -32602,
    "message": "Tool arguments failed validation.",
    "data": {
      "code": "invalid_arguments",
      "details": []
    }
  }
}

Logging

Both services log requests with method, path, status code, and elapsed time.

Log levels:

MCP_LOG_LEVEL=INFO
BACKEND_LOG_LEVEL=INFO

Tests

pytest -q

Tests cover:

  • Mock backend responses

  • REST facade responses

  • MCP tools/list

  • MCP tools/call

  • Tool validation

API Versioning Strategy

REST endpoints are versioned under /api/v1.

Recommended evolution:

  • Keep /api/v1 stable for existing Copilot plugins.

  • Add /api/v2 for breaking response or parameter changes.

  • Keep MCP tool names stable where possible.

  • Add new tool names for breaking tool contract changes.

  • Keep operationId values aligned with Copilot plugin function names.

References

  • Microsoft 365 Copilot plugins support REST APIs and MCP servers through declarative agent actions.

  • Current Microsoft plugin manifest schema version used here: v2.4.

  • Current Microsoft 365 app manifest schema used here: v1.22.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Exposes Azure AI Foundry agents, workflows, and AI Search vector-database capabilities as MCP tools, enabling natural language interaction with agents, semantic search, and index management.
    10
    2
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables querying Xubio accounting data (clients, invoices, products, balances) through natural language by exposing 52 read-only API endpoints as MCP tools.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Provides Claude agents with CRM contact lookup, action logging, and prompt cost auditing tools. Includes a Streamlit UI to demo the same tools without an MCP client.
    -