diff --git a/rfd/0209-mcp-access.md b/rfd/0209-mcp-access.md
new file mode 100644
index 00000000000..ac64f58eff5
--- /dev/null
+++ b/rfd/0209-mcp-access.md
@@ -0,0 +1,534 @@
+---
+authors: STeve Huang (xin.huang@goteleport.com)
+state: draft
+---
+
+# RFD 0209 - MCP Access
+
+## Required Approvers
+
+* Engineering: @r0mant
+* Product: @klizhentas
+
+## What
+
+Support zero-trust access for MCP servers.
+
+## Why
+
+Introduced in late 2024, Anthropic’s [Model Context Protocol
+(MCP)](https://modelcontextprotocol.io/introduction) is a widely adopted,
+open-source standard that enables language models to seamlessly interact with
+external tools and data, enhancing their contextual capabilities.
+
+However, MCP servers today are mostly operated locally without secure transport
+or authorization. OAuth authorization was recently added to the specification,
+but as of the time of writing, it is still new and not widely adopted.
+
+With Teleport's support for MCP servers, users can:
+- Access MCP servers on remote machines where transport is secured with TLS
+- Control access to specific MCP servers and specific tools defined by Teleport roles
+- Track activity through Teleport's audit log
+
+## Scope
+
+Teleport will support stdio-based MCP servers in the initial implementation,
+with [streamable
+HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http))
+and
+[OAuth](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization)
+support planned for future iterations (see the "Future development" section for
+details).
+
+## Details
+
+To expedite the initial rollout, the first iteration will focus on adding MCP
+protocol support for Application Access, similar to existing HTTP and TCP
+applications.
+
+### UX
+
+#### editor - configure a filesystem MCP server and define RBAC
+To configure a [filesystem MCP
+server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem)
+with `npx` and a [Slack API MCP
+server](https://github.com/modelcontextprotocol/servers/tree/main/src/slack)
+with `docker` via Teleport App service:
+```yaml
+app_service:
+ enabled: true
+ apps:
+ - name: "dev-files"
+ description: "Shared files for developers"
+ labels:
+ env: "dev"
+ mcp:
+ # Command and arguments to launch a stdio-based MCP server per client connection
+ # on the Teleport service host.
+ command: "npx"
+ args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/dev/files"]
+ # Specifies the local user account under which the command will be executed.
+ # Required for stdio-based MCP servers.
+ run_as_local_user: "dev"
+ # (Optional) Specifies the OS signal to send for gracefully stopping the
+ # process. Defaults to 0x2 (SIGINT) as it is a common signal for stopping
+ # programs listening on stdin. Signal 0x9 (SIGKILL) is sent automatically after
+ # 10 seconds if the process has not exited.
+ stop_signal: 0x2
+
+ - name: "dev-slack-api"
+ description: "Slack API to dev channels"
+ labels:
+ env: "dev"
+ mcp:
+ # Recommend containers for launching stdio-based MCP servers. Use an env file
+ # for passing secrets.
+ command: "docker"
+ args: ["run", "--rm", "--env-file", "/etc/teleport/dev_slack_api_env.list", "-i", "mcp/slack"]
+ run_as_local_user: "docker"
+```
+
+To create a Teleport role for developers that can access only dev MCP servers,
+have read-only filesystem access, and no permission to post Slack messages:
+```yaml
+kind: role
+version: v8
+metadata:
+ name: dev
+spec:
+ allow:
+ # Labels of the MCP servers to allow access to.
+ app_labels:
+ "env": "dev"
+ mcp:
+ # The name of the MCP tools to allow access to.
+ # The wildcard character '*' matches any sequence of characters.
+ # If the value begins with '^' and ends with '$', it is treated as a regular expression.
+ # This value field also supports variable interpolation.
+ # No tools are allowed if not specified.
+ tools:
+ - search_files
+ - ^(read|list|get)_.*$
+ - slack_*
+ - "{{internal.mcp_tools}}"
+ - "{{external.mcp_tools}}"
+
+ deny:
+ mcp:
+ # The name of the MCP tools to deny access to.
+ # No tools are denied if not specified.
+ # The deny rules always override allow rules.
+ tools:
+ - slack_post_message
+```
+
+To create a role for admins that have full access to MCP servers and tools:
+```yaml
+kind: role
+version: v8
+metadata:
+ name: admin
+spec:
+ allow:
+ app_labels:
+ "*": "*"
+ mcp:
+ tools: ["*"]
+```
+
+#### access - configure Claude Desktop to use the MCP server via Teleport
+
+First, to retrieve a list of allowed MCP servers:
+```bash
+$ tsh login ...
+...
+
+$ tsh mcp ls
+Name Description Type Labels
+------------- --------------------------- ----- -------
+dev-files Shared files for developers stdio env=dev
+dev-slack-api Slack API to dev channels stdio env=dev
+```
+
+To configure Claude Desktop:
+```bash
+$ tsh mcp login --all --format claude
+Logged into Teleport MCP servers:
+- dev-files
+- dev-slack-api
+
+Found Claude Desktop configuration at:
+/Users/alice/Library/Application\ Support/Claude/claude_desktop_config.json
+
+Claude Desktop configuration will be updated automatically. Logged in Teleport
+MCP servers will be prefixed with "teleport-" in this configuration.
+
+Run "tsh mcp logout" to remove the configuration from Claude Desktop.
+
+You may need to restart Claude Desktop to reload these new configurations. If
+you encounter a "disconnected" error when tsh session expires, you may also need
+to restart Claude Desktop after logging in a new tsh session.
+```
+
+After restarting Claude Desktop, Claude Desktop now shows a list of tools that
+are from server "teleport-dev-files" and "teleport-dev-slack-api".
+
+
+
+Note that only tools allowed by their Teleport roles will show up.
+
+Now start chatting with Claude to use these tools. For example, "can you
+retrieve me the content of file xxxx?", "can you search files related to xxxx?".
+
+#### auditor - track MCP server usage
+
+There are a few audit events related to MCP server sessions:
+- `mcp.session.start`/`mcp.session.end`: A start event and an end event is
+ expected per session from the client.
+- `mcp.session.notification`: Notifications that clients send to the server,
+ like `notifications/initialized`.
+- `mcp.session.request`: Requests that clients send to the server, like
+ `initialize`, `tools/call`. Frequent/basic discovery calls like `tools/list`,
+ `resources/list` are not recorded. `tools/call` that are access denied will also
+ have related errors recorded in the event.
+
+
+
+### Implementation details
+
+```mermaid
+sequenceDiagram
+ participant AI tool
+ participant tsh
+ participant Teleport
+ participant MCP server
+
+ AI tool -->> tsh: "tsh mcp connect"/stdio
+ tsh <<-->> Teleport: issue app cert
+ tsh -->> Teleport: local proxy with alpn "teleport-mcp"
+ Teleport <<-->> MCP server: "npx -y xxx"/stdio
+ Teleport -->> tsh:
+ tsh -->> AI tool:
+```
+
+#### Implementation - transport
+
+Per MCP protocol spec,
+[`stdio`](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports)
+is the primary transport that MCP uses:
+- the client launches the MCP server as a subprocess.
+- stdin for client-to-server messages.
+- stdout for server-to-client messages.
+- stderr for any information or errors that the MCP server wants to log outside
+ of communication with the client.
+
+For this initial implementation, `stdio` will be used between the AI tool and
+`tsh`'s local proxy, and between the App service and the MCP server getting
+launched on that host.
+
+`tsh` will use a local proxy and starts a TLS routing tunnel with the Proxy,
+using a new ALPN value `teleport-mcp`. Technically, the initial implementation
+can reuse ALPN `teleport-tcp` as TCP and MCP apps share the same app handler
+entrypoint. However, a new ALPN is introduced in case this entry point changes
+for MCP servers in the future.
+
+The duplex channel of the tunnelled TLS-routing connection to the Proxy will
+forward stdin and stdout respectively.
+
+#### Implementation - auth
+
+Since MCP servers are registered as apps, existing role option `app_labels` is
+used to control which MCP servers a role can access based on the labels of the
+MCP server apps.
+
+A new role option `mcp.tools` is introduced to apply fine-grained control on
+which tools from the MCP server should be allowed. Similar to
+`kubernetes_resources.name`, entries in `mcp.tools` also support glob and regex
+representations to make whitelisting or blacklisting easier. If `mcp.tools` is not
+specified for a role, all tools are allowed by default. This new option
+`mcp.tools` will be enforced per JSON-RPC `tools/list` and `tools/call`, which
+will be detailed in the next section.
+
+The procedure to access an MCP server from `tsh` is the same as any other app --
+a user has to obtain a user certificate with `route_to_app` for the TLS routing
+handshake, and the backend validates the identity then routes to the appropriate
+app service based on this certificate.
+
+#### Implementation - MCP server handler with JSON-RPC
+
+MCP server handler will be placed in App service's connection handler. The MCP
+server handler will receive a raw connection once the connection is authorized
+and confirmed to be an MCP app. It is expected [the MCP protocol in JSON-RPC
+format](https://modelcontextprotocol.io/specification/2025-03-26) will be
+transferred within this connection.
+
+Since using `stdio`, a `exec.Command` defined by the app definition is executed
+to start an MCP server, for each authorized incoming client connection. The
+execution will be run via the local user specified in `run_as_local_user`. Then
+stdin and stdout are proxied between the client connection and the launched MCP
+server. Stderr from the launched MCP server will be logged at TRACE level in
+Teleport logs.
+
+Incoming bytes from the client do get the following treatments before they are
+forwarded to the launched MCP server:
+- Parsed into MCP/JSON-RPC messages.
+- If the message is a `tools/call` request, use the access-checker to confirm
+ whether the identity has access to the requested tool.
+ - If the `tools/call` is access denied, make a text result detailing this
+ error and send it back to the client connection. Do not forward the request to
+ the launched MCP server in this case.
+- Record audit events for eligible messages.
+- Track the message type by the message ID.
+
+Incoming bytes from the server are processed as below:
+- Parsed into MCP/JSON-RPC messages.
+- Look up the message type by the message ID. If the message is a response for a
+ `tools/list` call, use the access-checker to filter out the tools not allowed
+ for this identity.
+
+The launched MCP server should be stopped once the connection ends. A stop
+signal (default SIGINT) is first sent to the launched process for a graceful
+termination. SIGKILL is sent automatically after 10 seconds if the process has
+not exited.
+
+#### Implementation - "tsh mcp" commands
+
+A suite of `tsh mcp` commands are added on the client side.
+
+Command `tsh mcp ls` lists MCP server apps:
+- Have slightly different columns from `tsh app ls`: `Name`, `Description`,
+ `Type`, and `Labels`.
+- Only supported `Type` is `stdio` for now. `HTTP` may come in the future.
+- `--verbose` flag enables outputting more columns like `Command`, `Args`,
+ `Allowed Tools`.
+- `--format json/yaml` is supported similar to other list commands.
+
+Command `tsh mcp login` deals with client configurations in addition to
+getting the app certificate:
+- Intake a list of MCP app names or `--all` to select all MCP servers.
+- If `--format json` is specified, print the common [`mcpServers` JSON
+ object](https://modelcontextprotocol.io/quickstart/user#mac-os-linux) that
+ includes the MCP servers getting logged it.
+- If `--format claude` is specified, detect if Claude Desktop is installed and
+ update the configuration automatically. If Claude Desktop configuration is not
+ found, treat it as `--format json`.
+- If `--format` is not specified, auto-detect which AI tools are installed (just
+ Claude Desktop for now) then prompt to update the config automatically.
+- Per selected MCP app, add an MCP server with name `teleport-`
+ and with command `tsh mcp connect ` to the configuration.
+
+Command `tsh mcp logout` removes Teleport MCP servers from client configurations
+in addition to removing the app certificates:
+- Intake a list of MCP app names or `--all` to select all MCP servers.
+- `--format claude` for Claude Desktop, which is also the default if `--format`
+ is not set.
+
+Command `tsh mcp connect` is called by the AI tool to launch an MCP server
+through Teleport:
+- User cert with `route_to_app` is automatically obtained if the app hasn't been
+ logged in yet.
+- stdin and stdout of this process are proxied to Teleport backend.
+- Any local tsh logs are sent to stderr.
+
+Command `tsh mcp db` is called by the AI tool to launch a local MCP server that
+provides a database query tool through Teleport Database access. This feature is
+outside this RFD but shares the same `tsh mcp` root.
+
+#### Compatibility with App access
+
+Since the MCP servers run as apps, here’s a list of some existing app
+functionalities and how they align with it:
+- Web UI: MCP servers will be listed as Application resources. Similar to TCP
+ apps, MCP servers cannot be "launched" through Web UI, and clicking "Connect"
+ button opens a dialog with `tsh` instructions.
+- VNet: not supported.
+- Per-session MFA: not supported. (Dependency on Tray app or MCP access support
+ in Teleport Connect)
+- Access Request: supported.
+- Dynamic registration: supported but requires new role option rules on new
+ resource kind `mcp_server`.
+- Audit events: will generate `cert.create` event when cert is requested, but no
+ `app.session.start` event.
+- `tsh`:
+ - `tsh app ls`: will be listed with `MCP (stdio)` as the type.
+ - `tsh app login`: internally calls `tsh mcp login`.
+ - `tsh app logout`: internally calls `tsh mcp logout`
+ - `tsh app config`: not supported.
+ - `tsh proxy app`: not supported.
+
+#### Usage reporting
+
+`mcp.session.start` events will be reported for existing session start events
+(`tp.session.start.v2`), with `mcp` as the session type. MCP sessions will be
+counted as regular `app_sessions` for user activity temporarily until new
+`mcp_sessions` is added on the cloud side.
+
+### Security
+
+Transport and auth wise, this feature is mostly the same with existing TCP
+access with the addition of the new role option `mcp.tools`.
+
+One potential concern is that the App service executes an arbitrary 3rd party
+command for each MCP session. To mitigate this, `run_as_local_user` is required
+when launching stdio-based MCP servers, allowing administrators to restrict the
+permissions and scope of the designated local user. Administrators can also run
+Docker commands to launch MCP servers within isolated containers. This is done
+today by configuring `docker` command, but in the future we could provide a
+dedicated docker section in the definition and use Docker APIs to launch the
+containers. Alternatively, once support for HTTP-based MCP servers is added, we
+can move away from stdio-based MCP servers.
+
+With `tools/list` filtering, AI clients are only provided with the tools they
+are explicitly allowed to call. They will typically only be able to invoke tools
+that appear in this list. However, nothing prevents a malicious AI client (or a
+dev tool) to call a tool that is not present in `tools/list`. Therefore, the
+implementation explicitly checks `tools/call` and denies them when necessary, in
+addition to `tools/list` filtering.
+
+## Future Development
+
+One key improvement that can be done is to move MCP servers to a standalone
+service, instead of relying on the App service, to support potential future
+expansion.
+
+Additional enhancements have been organized into two categories: "Phase 2", which
+includes near-term improvements planned immediately after the initial iteration,
+and "Other Improvements" which capture longer-term or lower-priority ideas.
+
+### Phase 2 - HTTP-based MCP servers
+
+[Streamable HTTP
+transport](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http)
+is introduced in spec 2025-03-26 replacing the "HTTP with SSE" transport. Many
+MCP servers has added streamable HTTP transport support like the [Everything MCP
+server](https://github.com/modelcontextprotocol/servers/blob/main/src/everything/README.md).
+Stdio-based MCP servers can also be easily converted to use streamable HTTP
+transport with help of converters like
+[mcp-proxy](https://github.com/sparfenyuk/mcp-proxy).
+
+Teleport should add support for MCP servers with streamable HTTP transport and
+favor it over stdio:
+- HTTP-based servers operate as a single persistent service instead of requiring
+ a new subprocess for each client session.
+- HTTP-based servers can be hosted and secured independently of the Teleport
+ process, allowing use of containers, secret stores, and other security
+ measures.
+
+Here is a sample config that connects to an HTTP-based MCP server:
+```yaml
+app_service:
+ enabled: true
+ apps:
+ - name: "everything"
+ uri: "mcp+http://localhost:3001/mcp"
+```
+
+`tsh mcp connect` by default serves stdio transport to the AI client tool but
+translates stdio to streamable HTTP before sending it to Teleport backend.
+
+The MCP server handler on the App service will run an in-memory HTTP server that
+handles the MCP protocol before forwarding the requests to the URI specified in
+the App definition.
+
+### Phase 2 - expired tsh session handling
+
+MCP server errors are permanent in Claude Desktop. When `tsh` session expires,
+the user has to run `tsh login` then restart Claude Desktop.
+
+
+
+To properly handle this, a mini JSON-RPC server should be served locally that
+ensures a good connection with Claude Desktop. When `tsh` session expires, the
+local JSON-RPC should instruct the user to perform a `tsh login` while
+re-establish the connection with Proxy when possible. Note that the local
+JSON-RPC server does NOT need to understand/implement the full MCP protocol as
+it only forwards the protocol to backend when things are good and rejects the
+request with hint when `tsh` session expires.
+
+### Other improvements - RBAC on resources and prompts
+
+Currently, MCP protocol supports three primitives: resources, tools, and
+prompts. We can extend `role.allow.mcp` for primitives other than tools:
+
+```yaml
+allow:
+ mcp:
+ tools:
+ - "*"
+ resources:
+ - "file:///var/log/*"
+ prompts:
+ - "code_review"
+```
+
+Note that both resources and prompts support
+[Completion](https://modelcontextprotocol.io/specification/2025-03-26/server/utilities/completion),
+which might also need some handling.
+
+### Other improvements - OAuth support
+
+There are several ways for Teleport to make use of [OAuth
+authorization](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) for MCP servers.
+
+1. Teleport Agent --> MCP servers: Teleport agent can use the "Client
+ Credentials" grant type to authorize MCP servers. Client ID and secrets can
+ potentially be saved as integration credentials.
+1. Teleport as IDP --> 3rd party MCP servers: Teleport can act as an OAuth
+ provider for AI clients, allowing them to authorize third-party MCP servers
+ they connect to directly.
+1. Teleport as IDP --> Teleport-protected MCP servers: Teleport can act as an OAuth
+ provider for AI clients, allowing them to connect through Teleport Proxy to MCP
+ servers hosted on Teleport agents.
+
+### Other Improvements - more AI tools support
+
+`tsh mcp login` currently only supports Claude Desktop. We should add support
+for other AI tools like VS Code Copilot, Cursor, Zed, etc.
+
+### Other Improvements - Machine ID integration
+
+Many AI agent SDKs like
+[OpenAI](https://openai.github.io/openai-agents-python/mcp/) have MCP support,
+so it would be great to have native `tbot` support to connect MCP servers
+through Teleport.
+
+### Other Improvements - Better runtime support
+
+Support for launching stdio-based MCP servers in containers via Docker or
+Kubernetes APIs is desirable. While the current implementation allows setting
+docker as the command with the necessary arguments, relying on `os/exec` is
+suboptimal. A more robust and secure approach would involve integrating directly
+with container runtimes or orchestration APIs without spawning subprocesses via
+`os/exec`.
+
+Another potential enhancement is to support variable interpolation in various
+process launch parameters, in the app or role definition. For example:
+```yaml
+spec:
+ allow:
+ app_labels:
+ "mcp-type": "filesystem"
+ mcp:
+ tools: ["*"]
+ run_as: "{{email.local(external.email)}}"
+```
+
+### Other Improvements - Teleport Connect
+
+Add MCP access support in Teleport Connect to provide a more user-friendly GUI
+experience compared to CLI/tsh.
+
+### Other Improvements - environment variable support
+
+Many MCP servers require extra environment variables to run. For example, the
+[Slack](https://github.com/modelcontextprotocol/servers/tree/main/src/slack) MCP
+server requires `SLACK_BOT_TOKEN` and a few other environment variables.
+
+The initial implementation will not support specifying environment variables in
+the app definition as it might become a security concern when secrets like
+`SLACK_BOT_TOKEN` are saved in the app definition.
+
+One workaround today is to launch the MCP server using Docker and specify the
+environment variables through an env file. Alternatively, users can set these
+environment variables when starting the Teleport process, since the MCP server
+inherits the Teleport process’s environment by default.
\ No newline at end of file
diff --git a/rfd/assets/0209-audit-events.png b/rfd/assets/0209-audit-events.png
new file mode 100644
index 00000000000..fb97b110948
Binary files /dev/null and b/rfd/assets/0209-audit-events.png differ
diff --git a/rfd/assets/0209-claude-desktop-disconnected.png b/rfd/assets/0209-claude-desktop-disconnected.png
new file mode 100644
index 00000000000..9cd3110f664
Binary files /dev/null and b/rfd/assets/0209-claude-desktop-disconnected.png differ
diff --git a/rfd/assets/0209-claude-desktop-tools.png b/rfd/assets/0209-claude-desktop-tools.png
new file mode 100644
index 00000000000..62c99d55761
Binary files /dev/null and b/rfd/assets/0209-claude-desktop-tools.png differ
diff --git a/rfd/cspell.json b/rfd/cspell.json
index e087d05ff72..e8f1cfa2c91 100644
--- a/rfd/cspell.json
+++ b/rfd/cspell.json
@@ -519,6 +519,7 @@
"mitigations",
"mkdocs",
"mmap",
+ "modelcontextprotocol",
"mongosh",
"moretags",
"msteams",
@@ -724,6 +725,7 @@
"stdtime",
"steganographically",
"stolonctl",
+ "streamable",
"stretchr",
"strideynet",
"struct",