Skip to main content

OpenAI MCP Extensions for Python

The Python SDK provides server-side OpenAI extensions for the official MCP Python SDK. Use @openai/mcp-extensions/app for MCP App extensions.

Installation

Install the Python extension SDK from PyPI.

uv add openai-mcp-extensions

Server Setup

Initialize the MCP Apps and OpenAI extensions for use with an MCP Server.

from mcp.server.apps import Apps
from mcp.server.mcpserver import MCPServer

from openai_mcp_extensions import OpenAIExtensions

apps = Apps()
openai_extensions = OpenAIExtensions()

Register extension handlers and resources before constructing MCPServer.

The following examples are separate configurations. Include each extension your server uses in its extensions list.

Structured Settings

from typing import Any, Literal

from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.context import Context
from pydantic import BaseModel, Field

from openai_mcp_extensions import (
    OpenAISettings,
    OpenAISettingsGroup,
    OpenAISettingsProperty,
)
from preferences import load_preferences, update_preferences


class Preferences(BaseModel):
    units: Literal["mm", "in"] = Field(title="Measurement units")
    show_grid: bool = Field(alias="showGrid", title="Show grid")


settings = OpenAISettings(
    schema=Preferences,
    # Optionally arrange fields into groups.
    # Omitted properties appear in an "Other settings" group below the listed groups.
    layout=[
        OpenAISettingsGroup(
            title="Display",
            items=[OpenAISettingsProperty(property="units"), OpenAISettingsProperty(property="showGrid")],
        ),
    ],
)


# Synchronous handlers run in a worker thread. Async handlers run on the event loop.
@settings.read
async def read_settings(context: Context[Any, Any]) -> Preferences:
    return await load_preferences(context)


# Synchronous handlers run in a worker thread. Async handlers run on the event loop.
@settings.update
async def update_settings(set: dict[str, Any], context: Context[Any, Any]) -> Preferences:
    return await update_preferences(set, context)


server = MCPServer(
    "viewer",
    extensions=[settings],
    # Only needed if your server does not support the 2026-07-28 spec and/or supports
    # the legacy initialize handshake.
    # https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization
    middleware=[settings.advertise_legacy_capability],
)

UI Entrypoints

from mcp.server.apps import APP_MIME_TYPE
from mcp.server.mcpserver.resources import TextResource
from mcp_types import Icon

from openai_mcp_extensions import (
    OpenAIFileEntrypoint,
    OpenAIGlobalEntrypoint,
    OpenAIThreadEntrypoint,
    OpenAIUiQuickAction,
    OpenAIUiQuickActionToolTarget,
    OpenAIUiResourceMetadata,
    OpenAIUiToolMetadata,
)

apps.add_resource(
    TextResource(
        uri="ui://table/viewer",
        name="table",
        mime_type=APP_MIME_TYPE,
        text="<!doctype html><title>Table</title><main>Table viewer</main>",
        meta={
            "openai/ui": OpenAIUiResourceMetadata(
                preferred_display_mode="fullscreen",
                available_display_modes=["inline", "fullscreen"],
            ).model_dump(by_alias=True, exclude_none=True),
        },
    ),
)


@apps.tool(
    resource_uri="ui://table/viewer",
    meta={
        "openai/ui": OpenAIUiToolMetadata(
            entrypoints=[
                OpenAIGlobalEntrypoint(
                    quick_action=OpenAIUiQuickAction(
                        title="New table",
                        icons=[Icon(src="https://example.com/plus.svg")],
                        target=OpenAIUiQuickActionToolTarget(name="create_table", arguments={}),
                    ),
                ),
                OpenAIThreadEntrypoint(),
                OpenAIFileEntrypoint(extensions=[".csv", ".tsv"]),
            ],
        ).model_dump(by_alias=True, exclude_none=True),
    },
)
def open_table() -> str:
    return "Open the table viewer."


server = MCPServer("my-server", extensions=[apps, openai_extensions])

Filesystem Access

from typing import Any

from mcp.server.mcpserver.context import Context

from openai_mcp_extensions import get_resource_path


def opened_file_path(context: Context[Any, Any]) -> str | None:
    return get_resource_path(context.request_context.meta)

Composer Mentions

from typing import Any

from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.context import Context
from mcp_types import ResourceLink

from openai_mcp_extensions import (
    OpenAIMentions,
    OpenAIMentionSearchParams,
    OpenAIMentionSearchResult,
)

mentions = OpenAIMentions()


@mentions.search
async def search_mentions(
    params: OpenAIMentionSearchParams,
    context: Context[Any, Any],
) -> OpenAIMentionSearchResult:
    return OpenAIMentionSearchResult(
        items=[
            ResourceLink(
                uri=f"mcp://issues/{params.query}",
                name=params.query,
            ),
        ],
    )


server = MCPServer("issue-tracker", extensions=[mentions])

Form Elicitation

Multi-round-trip Requests

For MCP 2026-07-28 or later, register the middleware and return pending input requests:

from typing import Any

from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.context import Context
from mcp_types import CallToolResult, InputRequiredResult
from pydantic import BaseModel

from openai_mcp_extensions import OpenAIExtensions

extensions = OpenAIExtensions()
server = MCPServer("forms", middleware=[extensions.middleware])


class ProjectForm(BaseModel):
    code: str


@server.tool()
async def choose_project(context: Context[Any, Any]) -> CallToolResult | InputRequiredResult:
    result = extensions.request_input(
        context, key="project", message="Enter a project code", schema=ProjectForm
    )
    if isinstance(result, InputRequiredResult):
        return result
    if result.action != "accept":
        return CallToolResult(content=[], structured_content={"action": result.action})
    return CallToolResult(content=[], structured_content=result.data.model_dump())

Suggested Values

Users can enter values that are not listed. The same field constraints apply to suggested and entered values.

from typing import Annotated

from pydantic import BaseModel, Field


class ReviewForm(BaseModel):
    purpose: str = Field(
        min_length=1,
        json_schema_extra={
            "x-openai-suggestions": [{"const": "prototype", "title": "Prototype"}],
        },
    )
    checks: list[
        Annotated[
            str,
            Field(
                min_length=1,
                json_schema_extra={
                    "x-openai-suggestions": [{"const": "clearance", "title": "Clearance"}],
                },
            ),
        ]
    ]

Resource Selection

from typing import Any

from mcp.server.elicitation import ElicitationResult
from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.context import Context
from mcp_types import Resource
from pydantic import BaseModel, Field, FileUrl

from openai_mcp_extensions import OpenAIExtensions
from openai_mcp_extensions.form import UserResourceOptions, resource_input

openai_extensions = OpenAIExtensions()
server = MCPServer("presentations", extensions=[openai_extensions])


class PresentationForm(BaseModel):
    images: list[FileUrl] = Field(
        default_factory=list,
        max_length=5,
        json_schema_extra={
            **resource_input(
                options=[
                    Resource(
                        uri="file:///images/sales.png",
                        name="sales.png",
                        title="Sales image",
                        meta={
                            "openai/thumbnail": {"src": "https://example.com/sales.png"},
                            "openai/preview": {
                                "target": {
                                    "type": "resource_link",
                                    "uri": "file:///images/sales.png",
                                    "name": "sales.png",
                                    "mimeType": "image/png",
                                },
                            },
                        },
                    ),
                ],
                user_options=UserResourceOptions(accept=["image/*"]),
            ),
            "default": ["file:///images/sales.png"],
        },
    )


@server.tool()
async def choose_images(context: Context[Any, Any]) -> ElicitationResult[PresentationForm]:
    return await openai_extensions.elicit_input_legacy(
        context,
        mode="form",
        message="Choose reference images",
        schema=PresentationForm,
    )

Metadata

Release files for openai-mcp-extensions 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openai-mcp-extensions 0.2.0
File Size Uploaded
openai_mcp_extensions-0.2.0.tar.gz 27.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for openai-mcp-extensions 0.2.0
File Interpreter ABI Platform
openai_mcp_extensions-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 55.2 kB

Release files / openai_mcp_extensions-0.2.0.tar.gz

Download URL openai_mcp_extensions-0.2.0.tar.gz
Size 27.3 kB
Tags Source
SHA-256 checksum
How to use checksums
7e8b09b5283d0f02a9a15b7ee2862f536eb4b34ae06bb2941383fbdcca0253b5
BLAKE2b-256 checksum
How to use checksums
9b967245cb6efab1f3b7846fc079909da7888ae895cb40efd87c3a7a5856d791
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.

Transparency log

Release files / openai_mcp_extensions-0.2.0-py3-none-any.whl

Download URL openai_mcp_extensions-0.2.0-py3-none-any.whl
Size 27.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a7a50cd0367db6d646160a268ed7a333fd83a6b1011f91df7dc78729cc349d3f
BLAKE2b-256 checksum
How to use checksums
5f8214f6c685389d5b1978efebfa7fc7e0a4ab458d1939b3aedab94ca15efbbd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page