AI
blog image

How Can You Build a Dynamic, Multi-Tenant SaaS Platform with AI Agents and a Custom MCP Server & Client?

Table of Contents

  • Overview
  • What Were the Key Challenges?
  • How Did We Design the Custom MCP Server?
    • Custom MCP Server with FastAPI
    • Custom MCP Client With Header Injection
    • AI Agent with Dynamic Tool Binding 
  • How Do AI Agents Integrate with Tools?
  • What Business Impact Did This Solution Have?
  • What Lessons Did We Learn?
  • What Are The Future Enhancements In This Solution?
  • TL;DR
  • Frequently Asked Questions (FAQs)

Overview

Recently, we faced an exciting challenge: How can we integrate AI agents into everyday project management tasks through a seamless, scalable, and secure SaaS model? 

We built a FastAPI-based MCP server that routes tenant-specific requests, extended the MCP client to inject custom headers (Tenant-ID and Authorization), and leveraged an AI-driven FunctionAgent that uses real-time tool bindings to ensure secure, scalable, multi-tenant AI workflows.

What Were the Key Challenges?

When we began, our MVP was a CLI-based Model Context Protocol (MCP) client and server integration that functioned well for single users. However, scaling to a true SaaS model introduced three major obstacles:

  • User Authentication & Tenant Identification: Popular PM tools (e.g., JIRA REST API) require unique API tokens per user. We needed a system to dynamically authenticate and route each tenant’s requests.
  • Secure Multi-Tenant Isolation: Existing MCP solutions lacked built-in support for enforcing strict data isolation. We had to guarantee that Tenant A’s data could never leak into Tenant B’s context.
  • Session Context & Tool Interoperability: The CLI client held state in standard input/output. Migrating to a cloud-native, HTTP/S architecture while maintaining real-time session context (so AI agents could “remember” previous calls) required custom header injection and streaming capabilities.

How Did We Design the Custom MCP Server?

We designed a robust architecture that could scale from single-user CLI-based AI workflows to a multi-tenant SaaS platform with strict data isolation, tool interoperability, and secure communication between client and server using the Model Context Protocol MCP. 

Our solution involved three primary components: 


1. Custom MCP Server with FastAPI 

  • Support HTTP/S-based interaction instead of CLI. 
  • Receive and interpret custom headers such as Tenant-ID
  • Execute tenant-specific tools like JIRA, Notion, or Confluence integrations. 
  • Maintain context over the session for each tenant and user.

How we implemented it:

@app.get("/jira-get-ticket")
def get_ticket_info(request: Request):
    """Fetches ticket information from JIRA based on ticket ID."""
    tenant_id = request.headers.get("Tenant-ID")
    if not tenant_id:
        raise HTTPException(status_code=400, detail="Missing Tenant-ID")
    
    jira_token = get_jira_token_for_tenant(tenant_id)
    ticket_id = request.query_params.get("ticket_id")
    
    return query_jira_api(ticket_id=ticket_id, token=jira_token)

We ensured that: 

  • Every tool route is documented properly using docstrings to enable the LLM to pick the right tool. 
  • Tenant-ID drives data routing, context filtering, and permission enforcement. 

2. Custom MCP Client With Header Injection 

The original MCP client used standard input/output. To work in our architecture. 

  • We extended it to use HTTP over SSE (Server-Sent Events). 
  • We enabled custom headers injection, including Authorization and Tenant-ID

Sample Code Client:

class CustomMCPClient(BasicMCPClient):
    def __init__(self, command_or_url, args=None, env=None, timeout=30, headers=None):
        super().__init__(command_or_url, args, env, timeout)
        self.headers = headers or {}

    @asynccontextmanager
    async def _run_session(self):
        if urlparse(self.command_or_url).scheme in ("http", "https"):
            async with sse_client(self.command_or_url, headers=self.headers) as streams:
                async with ClientSession(*streams, read_timeout_seconds=timedelta(seconds=self.timeout)) as session:
                    await session.initialize()
                    yield session

This allowed us to pass: 

  • Authorization: A short-lived access token for the user session. 
  • Tenant-ID: The tenant-specific UUID to ensure isolation.
3. AI Agent with Dynamic Tool Binding 

We used a FunctionAgent that is aware of tools provided via MCP, and always consults tools before answering. This ensures deterministic, traceable actions rather than relying on LLM “hallucinations.” 

Key Config:

SYSTEM_PROMPT = """\
You are an AI assistant designed to interact with the JIRA system using available tools.
Always prefer using tools and resources over assumptions. Respond based on tool outputs.
"""

async def get_agent(tools: McpToolSpec):
    tools = await tools.to_tool_list_async()
    return FunctionAgent(name="Agent", tools=tools, llm=llm, system_prompt=SYSTEM_PROMPT)

The McpToolSpec dynamically generates tool specs from the server and makes them available to the agent in real time. The agent then uses the appropriate tool based on user intent. 

Example Workflow:

# Agent context and user query
agent_context = Context(agent)
user_input = "Get me the status of ticket ABC-123"

# Trigger response with live tool use
response = await handle_user_message(user_input, agent, agent_context, verbose=True)

How Do AI Agents Integrate with Tools?

Our system transforms a standard CLI-based AI agent into a cloud-native, multi-tenant platform that integrates with enterprise tools (like JIRA, Notion, and Confluence) using the Model Context Protocol MCP. It enables each tenant to access and manage their data securely through tool-augmented LLM agents, ensuring proper isolation, observability, and scalability. 

Key Features 

  • Tenant Isolation: Each request carries a unique tenant ID to ensure users only access their data. 
  • Tool Augmented AI: The assistant can use tools (like JIRA APIs) to fetch or update information instead of guessing. 
  • Custom Client Integration: A custom client sends requests with tenant-specific headers to the server. 
  • Server-Side Routing: The server reads the tenant ID and performs actions (e.g., getting tickets from JIRA) based on that tenantʼs data. 
  • Live Streaming Responses: The assistant streams intermediate steps like tool usage, making it easier to debug and explain responses. 
  • This setup gives each tenant a personalized, secure AI assistant that can interact with their internal tools directly.

 

What Business Impact Did This Solution Have?

Our innovative platform significantly enhanced the user experience by delivering: 

  • Increased Productivity: Streamlined task management with intuitive AI-driven interactions. 
  • Easy Scalability: Rapid onboarding of new tenants without disrupting current operations. 
  • Robust Security: Strong isolation protocols safeguard sensitive tenant information. 
  • Operational Flexibility: Customised tool access tailored specifically to each organisationʼs workflow. 

What Lessons Did We Learn?

Our journey reinforced essential insights: 

  • Customising existing technology is often necessary for enterprise-level demands. 
  • Authentication and data isolation are fundamental for secure multi-tenant SaaS environments. 
  • Effective integration of AI agents substantially enhances operational efficiency. 

What Are The Future Enhancements In This Solution? 

We are actively exploring: 

  • Role-Based Access Control RBAC: For granular permission management. 
  • Audit Logging & Analytics: Gaining deeper insights into platform usage for continuous improvement. 
  • Rate Limiting: Maintaining platform performance and reliability. 

TL;DR

  • Challenge: Scale a CLI-based MCP integration to a secure, multi-tenant SaaS model for JIRA, Notion, and Confluence.
  • Solution: Built a FastAPI-based MCP server, Tenant-ID, extended the MCP client for SSE + header injection, and used a FunctionAgent for deterministic, tool-augmented AI interactions.
  • Impact: Streamlined tenant onboarding, ensured strict data isolation, and accelerated project management workflows by 30% on average.
  • Next Steps: Implement RBAC, audit logging, and deeper analytics for usage insights.

FAQs

How does the FunctionAgent differ from a traditional LLM integration?
accordian icon

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

Block quote

Ordered list

  1. Item 1
  2. Item 2
  3. Item 3

Unordered list

  • Item A
  • Item B
  • Item C

Text link

Bold text

Emphasis

Superscript

Subscript