Skip to main content
Glama

create_product

Add new products or services to Stream by specifying name, price, type (one-off, recurring, or metered), and billing details for accurate setup.

Instructions

Create a new product or service in Stream.

type is ONE_OFF, RECURRING, or METERED. For recurring products, specify recurring_interval (WEEK, MONTH, SEMESTER, YEAR).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYes
typeNoONE_OFF
priceNo
currencyNoSAR
descriptionNo
is_price_inclusive_of_vatNo
is_price_exempt_from_vatNo
recurring_intervalNo
recurring_interval_countNo

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault

No arguments

Implementation Reference

  • The create_product tool handler function that creates a new product in Stream. It accepts product parameters (name, type, price, currency, etc.), creates a CreateProductRequest body, and posts to the API via the StreamClient.
    async def create_product(
        name: str,
        type: Literal["ONE_OFF", "RECURRING", "METERED"] = "ONE_OFF",
        price: float = 1.0,
        currency: str = "SAR",
        description: str | None = None,
        is_price_inclusive_of_vat: bool = True,
        is_price_exempt_from_vat: bool = False,
        recurring_interval: str | None = None,
        recurring_interval_count: int = 1,
        ctx: Context = None,  # type: ignore[assignment]
    ) -> dict[str, Any]:
        """Create a new product or service in Stream.
    
        *type* is ``ONE_OFF``, ``RECURRING``, or ``METERED``.
        For recurring products, specify *recurring_interval* (WEEK, MONTH, SEMESTER, YEAR).
        """
        prices = [ProductPriceInlineCreate(
            currency=currency,
            amount=price,
            is_price_inclusive_of_vat=is_price_inclusive_of_vat,
            is_price_exempt_from_vat=is_price_exempt_from_vat,
        )]
        body = CreateProductRequest(
            name=name,
            type=type,
            description=description,
            prices=prices,
            recurring_interval=recurring_interval,
            recurring_interval_count=recurring_interval_count,
        )
        client = await get_client(ctx)
        try:
            return await client.post(_BASE, body.model_dump(exclude_none=True))
        except StreamAPIError as exc:
            return _err(exc)
  • CreateProductRequest Pydantic model defining the schema for creating products with validation for name, type, description, prices, recurring intervals, etc.
    class CreateProductRequest(BaseModel):
        """Request body for creating a new product."""
    
        name: str = Field(..., min_length=1, max_length=160, description="Product display name.")
        type: Literal["ONE_OFF", "RECURRING", "METERED"] = Field(
            ...,
            description="Product billing type: ONE_OFF, RECURRING, or METERED.",
        )
        description: str | None = Field(default=None, max_length=500, description="Product description.")
        prices: list[ProductPriceInlineCreate] | None = Field(default=None, description="List of prices for different currencies.")
        is_one_time: bool = Field(default=False, description="Whether the product is one-time use.")
        recurring_interval: str | None = Field(
            default=None,
            description="Billing interval for recurring products: WEEK, MONTH, SEMESTER, YEAR.",
        )
        recurring_interval_count: int = Field(default=1, ge=1, description="Number of intervals between billings.")
  • ProductPriceInlineCreate Pydantic model defining the price structure (currency, amount, VAT flags) used within CreateProductRequest.
    class ProductPriceInlineCreate(BaseModel):
        """Inline price definition for product creation."""
    
        currency: str = Field(default="SAR", description="ISO-4217 currency code (SAR, USD, EUR, etc.).")
        amount: float = Field(..., ge=1, description="Price amount (≥ 1).")
        is_price_inclusive_of_vat: bool = Field(default=True, description="Whether price includes VAT.")
        is_price_exempt_from_vat: bool = Field(default=False, description="Whether price is VAT-exempt.")
  • The register function that registers the create_product tool with FastMCP using the @mcp.tool decorator.
    def register(mcp: FastMCP) -> None:
        """Register all product tools on *mcp*."""
    
        @mcp.tool
        async def create_product(
            name: str,
            type: Literal["ONE_OFF", "RECURRING", "METERED"] = "ONE_OFF",
            price: float = 1.0,
            currency: str = "SAR",
            description: str | None = None,
            is_price_inclusive_of_vat: bool = True,
            is_price_exempt_from_vat: bool = False,
            recurring_interval: str | None = None,
            recurring_interval_count: int = 1,
            ctx: Context = None,  # type: ignore[assignment]
        ) -> dict[str, Any]:
            """Create a new product or service in Stream.
    
            *type* is ``ONE_OFF``, ``RECURRING``, or ``METERED``.
            For recurring products, specify *recurring_interval* (WEEK, MONTH, SEMESTER, YEAR).
            """
            prices = [ProductPriceInlineCreate(
                currency=currency,
                amount=price,
                is_price_inclusive_of_vat=is_price_inclusive_of_vat,
                is_price_exempt_from_vat=is_price_exempt_from_vat,
            )]
            body = CreateProductRequest(
                name=name,
                type=type,
                description=description,
                prices=prices,
                recurring_interval=recurring_interval,
                recurring_interval_count=recurring_interval_count,
            )
            client = await get_client(ctx)
            try:
                return await client.post(_BASE, body.model_dump(exclude_none=True))
            except StreamAPIError as exc:
                return _err(exc)
  • The get_client helper function used by create_product to obtain a StreamClient for making API requests, supporting both local (lifespan) and remote (Bearer token) modes.
    async def get_client(ctx: "Context") -> StreamClient:
        """Return a :class:`StreamClient` for the current request.
    
        Resolution order:
    
        1. **Lifespan client** — used in local / stdio mode where a single
           ``STREAM_API_KEY`` is set as an environment variable.
        2. **Per-user client** — used in remote mode where each user passes
           their own API key as a Bearer token and (optionally) a custom
           base URL via the ``X-Stream-Base-URL`` header.
        """
        # ── 1. Local mode: shared client from server lifespan ─────────────
        shared_client = ctx.lifespan_context.get("client")
        if shared_client is not None:
            return shared_client
    
        # ── 2. Remote mode: per-user client from Bearer token ─────────────
        api_key = current_api_key.get()
        if not api_key:
            raise StreamError(
                "No Stream API key found. "
                "In local mode, set the STREAM_API_KEY env var. "
                "In remote mode, pass your key as a Bearer token in the Authorization header."
            )
    
        base_url = current_base_url.get() or settings.stream_base_url
        cache_key = f"{api_key}::{base_url}"
    
        if cache_key not in _client_cache:
            client = StreamClient(
                api_key=api_key,
                base_url=base_url,
                timeout=settings.stream_timeout,
                max_retries=settings.stream_max_retries,
            )
            await client.__aenter__()
            _client_cache[cache_key] = client
            logger.info(
                "Created cached StreamClient for remote user (key=…%s, base=%s)",
                api_key[-4:], base_url,
            )
    
        return _client_cache[cache_key]
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It mentions that 'type' can be ONE_OFF, RECURRING, or METERED, and that recurring products need 'recurring_interval', but doesn't address permissions, side effects, error conditions, or what happens after creation. For a mutation tool with zero annotation coverage, this is insufficient.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is efficiently structured with a clear main sentence followed by bullet-point style parameter guidance. Both sentences earn their place by providing essential information. It could be slightly more front-loaded with a broader usage context, but overall it's appropriately sized.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given that this is a mutation tool with 9 parameters, 0% schema description coverage, and no annotations, the description should do more to explain behavior and parameter usage. The presence of an output schema reduces the need to describe return values, but the description still lacks sufficient context for safe and effective use of this creation tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds meaningful context for two parameters ('type' and 'recurring_interval') beyond what the schema provides, explaining the enum values and their implications. However, with 9 total parameters and 0% schema description coverage, it leaves 7 parameters completely undocumented. The description compensates somewhat but not enough for the large coverage gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Create a new product or service') and the target system ('in Stream'), which provides a specific verb+resource combination. However, it doesn't explicitly differentiate this tool from its sibling 'update_product', which handles modifications rather than creation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'update_product' or 'list_products'. It mentions that recurring products require specifying 'recurring_interval', but this is parameter guidance rather than usage context. No exclusions, prerequisites, or alternative tools are mentioned.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/streampayments/stream'

If you have feedback or need assistance with the MCP directory API, please join our Discord server