Skip to main content

What are Connectors?

A connector is a local MCP server exposed by the Agent Stack that acts as a proxy to a remote hosted MCP server and handles authentication. It allows users to set up a connection to a third-party service (like GitHub, Box etc.) and then exposes a local MCP server that can be consumed by agents. This architecture eliminates the need for user authentication in your agent code. The connector handles authentication with the third-party service on behalf of the authenticated user, and the MCP interface is properly scoped to that user’s permissions and data. Users can work with connectors in two ways via the Agent Stack UI:
  • Connect an existing connector: Go through an OAuth flow to obtain an authentication token for a pre-registered connector.
  • Register a custom connector: Provide OAuth client ID, client secret, and URL to register their own service as a connector, then connect it.
When a connector is registered and connected, it becomes available for the agents to use through the MCP extension. Each connector has a name (like “github”, “slack”, or “notion”) that agents can reference when requesting MCP access. The platform automatically routes agent’s MCP requests to the appropriate connector, ensuring secure, user-scoped access to third-party services.

MCP Service Extension

The MCP Service Extension provides a standardized way for your agents to access MCP servers (connectors) without managing connection details, authentication, or URLs manually. The extension automatically matches your agent’s MCP requirements with available connectors and provides you with a ready-to-use MCP client session, eliminating the need for manual configuration. One of the key features of Agent Stack’s MCP integration is automatic connector matching. When you specify a suggested connector name in your agent, the platform automatically:
  1. Searches for connected connectors with matching names (case-insensitive)
  2. Creates an MCP client connection to the matched connector
  3. Provides you with a ready-to-use MCP client session
This means you don’t need to manually configure URLs or manage connector connections. The platform handles it all for you.
Service Extensions are a type of A2A Extension that allows you to easily “inject dependencies” into your agent. This follows the inversion of control principle where your agent defines what it needs, and the Client (in this case, Agent Stack UI) is responsible for providing those dependencies.
Service extensions are optional by definition, so you should always check if they exist before using them.

Quickstart

1

Add MCP service extension to your agent

Import the necessary components and add the MCP service extension to your agent function.
2

Specify your connector preference

Use the suggested parameter to indicate which connector you want to use (e.g., "github").
3

Create an MCP client

Use mcp_service.create_client() to get a connected MCP client session.
4

Use MCP tools

Use the MCP client session to call tools, list resources, or interact with prompts.

Example: GitHub MCP Agent

Here’s a complete example of an agent that uses the GitHub MCP connector:
import json
from collections.abc import AsyncGenerator
from typing import Annotated

from a2a.types import Message
from mcp import ClientSession

from agentstack_sdk.a2a.extensions.services.mcp import MCPServiceExtensionServer, MCPServiceExtensionSpec
from agentstack_sdk.a2a.types import RunYield
from agentstack_sdk.server import Server

server = Server()


@server.agent()
async def github_mcp_agent(
    mcp_service: Annotated[
        MCPServiceExtensionServer,
        MCPServiceExtensionSpec.single_demand(suggested=("github",)),
    ],
) -> AsyncGenerator[RunYield, Message]:
    """Lists tools"""

    if not mcp_service:
        yield "MCP extension hasn't been activated, no tools are available"
        return

    async with mcp_service.create_client() as client:
        if client is None:
            yield "MCP client not available."
            return

        read, write = client
        async with ClientSession(read_stream=read, write_stream=write) as session:
            await session.initialize()
            me_result = await session.call_tool("get_me", {})
            result_dict = me_result.model_dump() if hasattr(me_result, "model_dump") else me_result
            yield json.dumps(result_dict, indent=2, default=str)


if __name__ == "__main__":
    server.run()

Custom MCP Client with OAuth

If you prefer not to use the MCP Service Extension (which automatically matches connectors), you can still create a standard MCP client and use the OAuth extension for user authentication. Unlike the connector-based approach, this method does not require users to set up a connection ahead of the conversation. The OAuth extension handles the authentication flow through the Agent Stack UI, allowing users to authorize your agent to access third-party services during the conversation itself. This makes it ideal for scenarios where you want users to authenticate on-demand without pre-configuration.

Example: Direct MCP Client with OAuth

Here’s how to build an agent that creates a custom MCP client and uses OAuth for authentication:
import os
from typing import Annotated

import pydantic
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client

from agentstack_sdk.a2a.extensions.auth.oauth import OAuthExtensionServer, OAuthExtensionSpec
from agentstack_sdk.server import Server

server = Server()


@server.agent()
async def custom_mcp_oauth_agent(
    oauth: Annotated[OAuthExtensionServer, OAuthExtensionSpec.single_demand()],
):
    """Agent that uses OAuth to authenticate with a custom MCP server"""
    mcp_url = "https://mcp.stripe.com"

    if not oauth:
        yield "OAuth extension not available. Authentication required."
        return

    async with streamablehttp_client(
        url=mcp_url,
        auth=await oauth.create_httpx_auth(resource_url=pydantic.AnyUrl(mcp_url))
    ) as (read, write, _):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool("get_stripe_account_info")
            # Extract text content from CallToolResult
            if result.content:
                content = result.content[0]
                if hasattr(content, 'text'):
                    yield str(content.text)  # type: ignore
            else:
                yield "No content returned"


def run():
    server.run(host=os.getenv("HOST", "127.0.0.1"), port=int(os.getenv("PORT", "8000")))


if __name__ == "__main__":
    run()

Key Differences

MCP Service Extension approach:
  • Automatically matches connectors by name
  • No need to specify MCP server URLs
  • Handles connection setup automatically
  • Requires users to set up connectors before the conversation
  • Best for using registered connectors
Custom MCP Client with OAuth approach:
  • Manual MCP client creation with explicit URLs
  • Full control over connection parameters
  • Uses OAuth extension for in-conversation user authentication
  • No pre-setup required from users
  • Best for direct MCP server connections or on-demand authentication
Always check if the OAuth extension is available before using it to comply with plain A2A clients that may not support OAuth authentication.