MCP Servers on Connect: Managing credentials and access
In Part 1, we covered what the Model Context Protocol (MCP) is, how we are using it at Posit, and how to deploy an MCP server to return the weather for a specific location using latitude and longitude data. It’s a useful example of how they work, but the MCP servers your team wants to use have access to your data warehouse, internal APIs, or other resources. Building those comes with real questions about credentials and access that our weather example didn’t address.
This post walks through building an MCP server that queries your Databricks Unity Catalog and how Connect handles the authentication of users so you don’t have to build it yourself. Although this example uses Databricks, Connect supports a wide range of third-party integrations such as Snowflake, Azure, AWS, Google Vertex, and others.
Building with IT and security in mind
We added features to Connect to support our internal MCP servers based on feedback from our IT and security teams regarding credential flows, access controls, and governance. If you’re thinking about deploying an MCP server, here are some of the things that we discussed that may be relevant for your discussions with your security teams.
Authentication. You need to prove who is making each request. A public weather API doesn’t need to know who you are, but your data warehouse does.
Per-User Access. Depending on your use case, different users should see different data based on their permissions. A shared service account might work for some situations, but it can be an overly permissive access pattern.
Credential Management. The MCP server needs to connect to internal data, meaning user credentials are involved. Your security team doesn’t want those hardcoded in your code or sitting in a plain-text configuration file for each user.
Connect makes it easy to deploy MCP servers by providing user authentication and access controls that are easy to establish and maintain. We already have the above governance and controls built into Connect.
Building a Databricks MCP Server
Here’s an MCP server that lets an AI client query the NYC taxi trip data on Databricks with a SQL warehouse. This should look similar to our weather MCP. We use FastMCP as the framework, a @mcp.tool decorator, and Python functions. Note we set two environment variables for the Databricks server hostname (DATABRICKS_HOST) and the HTTP Path (DATABRICKS_PATH). These are accessible in your Databricks workspace under the connection details for SQL warehouses.
# server.py
import os
from urllib.parse import urlparse
from fastmcp import FastMCP
from fastmcp.server.dependencies import get_http_headers
from starlette.middleware.trustedhost import TrustedHostMiddleware
from databricks import sql
from posit.connect.external.databricks import (
ConnectStrategy,
databricks_config,
sql_credentials,
)
DATABRICKS_HOST = os.getenv("DATABRICKS_HOST", "")
DATABRICKS_SERVER_HOSTNAME = DATABRICKS_HOST.replace("https://", "")
SQL_HTTP_PATH = os.environ["DATABRICKS_PATH"]
mcp = FastMCP(
name="Databricks Trips MCP Server",
instructions=(
"MCP server that exposes NYC taxi trip data from a Databricks SQL "
"warehouse. Use the query_trips tool to fetch sample rows."
),
)
@mcp.tool()
def query_trips(limit: int = 10) -> str:
"""Query NYC taxi trip data from Databricks."""
session_token = get_http_headers().get("posit-connect-user-session-token")
cfg = databricks_config(
posit_connect_strategy=ConnectStrategy(user_session_token=session_token),
)
with sql.connect(
server_hostname=DATABRICKS_SERVER_HOSTNAME,
http_path=SQL_HTTP_PATH,
credentials_provider=sql_credentials(cfg),
) as connection:
with connection.cursor() as cursor:
cursor.execute(f"SELECT * FROM samples.nyctaxi.trips LIMIT {int(limit)}")
rows = cursor.fetchall()
columns = [col[0] for col in cursor.description]
lines = [" | ".join(columns)]
for row in rows:
lines.append(" | ".join(str(v) for v in row))
return "\n".join(lines)
app = mcp.http_app(path="/mcp", stateless_http=True, json_response=True)
if connect_server := os.environ.get("CONNECT_SERVER", ""):
host = (urlparse(connect_server).netloc or connect_server).rstrip("/")
if host:
app.add_middleware(TrustedHostMiddleware, allowed_hosts=[host])Let’s walk through how we manage credentials in this example.
get_http_headers() retrieves a session token that tells Connect which user is making the request. When someone uses their AI client to call this server, Connect passes along a token that identifies them.
ConnectStrategy() and sql_credentials()come from our posit-sdk package. They are helper functions we’ve built to support using our integrations with Databricks, so you can safely pass per-user credentials to Databricks.
This means you write the query logic and Connect handles the authentication and credential plumbing with the integrations already configured on your server. Connect doesn’t store a long-lived user credential, which is something your security team will appreciate.
How Connect manages credentials
It’s worth a deeper dive on how the credential flow works, because this is the part that matters most when you’re moving from running MCP servers locally to a hosted, production use case.
Connect includes an OAuth 2.1 authorization server. OAuth 2.1 is a security standard that is part of the MCP specification, which allows third-party applications to authenticate users on their behalf. So when a Connect user wants their AI client to authenticate with Connect, they do so through a standard browser-based login. There are no API keys stored on a user’s local machine, and it’s a well-vetted pattern that will be familiar to your security team. For development or testing purposes, you can use API keys if you have them enabled.
When the MCP server on Connect needs to query Databricks, you can use Connect’s built-in viewer integrations to handle that credential exchange. Connect requests a scoped token from Databricks on behalf of that specific user. User A and User B can call the same MCP server and get different results because the data they see is determined by their own Databricks permissions, not a shared service account.
Governance is part of Connect
Access controls for MCP servers work just like any other API or content item. You can control who can manage and access each MCP server. Connect logs which user, via their MCP client, accessed each MCP server. Administrators can view, register, and remove connected clients in the System page. When thinking about governance in Connect, here are some common questions.
How do users authenticate? Through Connect’s OAuth 2.1 server, a standard browser-based flow. No API keys in config files, no credentials on user machines.
Where do the database credentials live? A key advantage of MCP servers in this case is that your AI client never sees, let alone stores, the credentials. Connect brokers them on a per user basis through viewer integrations. Credentials are hidden from the MCP server and even publishers that configure the integrations.
Who controls access? Publishers decide who can see and use each MCP server. Administrators can manage what integrations are available to publishers. Connect integrates with your Identity Provider so existing groups can be centrally managed, with support for the System for Cross-domain Identity Management (SCIM).
Is there an audit trail? Yes. Connect provides usage data that lets you see who used your MCP server and when. Administrators can use the audit logs that capture changes to the system such as deploying a new server, creating an integration, or granting access to a server. And since Connect’s integrations can capture per-user credentials instead of a shared account, the queries appear in Databricks (and other providers) under each user’s own identity in their internal logs. Importantly, none of this is specific to MCP servers. This data is available for every application and document on Connect.
What’s next?
This is part of an ongoing series on hosting MCP servers on Connect, and we’ll keep it going in future posts as we continue building out MCP support.