Skip to main content
Glama

tiktok_publish

Publish videos to TikTok using automated browser sessions. Upload video files, add captions, and include hashtags through authenticated Creator Center access.

Instructions

Publish a video to TikTok. Requires authenticated session. Uses browser automation via Creator Center.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
video_pathYesPath to the video file to upload
captionYesVideo caption/description
hashtagsNoList of hashtags (without #)

Implementation Reference

  • The publish_video method uses Playwright to navigate to the TikTok Creator Center, upload the specified video file, add a caption and hashtags, and click the post button.
    async def publish_video(
        self,
        video_path: str,
        caption: str,
        hashtags: list[str] = None,
    ) -> dict:
        """Publish a video to TikTok via Creator Center."""
        result = {"success": False}
    
        if not os.path.exists(video_path):
            result["error"] = f"Video file not found: {video_path}"
            return result
    
        try:
            page = await self.goto_tiktok("/creator-center/upload")
            await asyncio.sleep(5)
    
            # Check if redirected to login
            if "login" in page.url:
                result["error"] = "Not authenticated. Run tiktok_session with action=check_login first."
                return result
    
            # Find file input and upload
            file_input = await page.query_selector('input[type="file"]')
            if not file_input:
                # Sometimes hidden, try to find it
                file_input = await page.wait_for_selector('input[type="file"]', timeout=15000)
    
            if not file_input:
                result["error"] = "Could not find upload input"
                return result
    
            await file_input.set_input_files(os.path.abspath(video_path))
            logger.info(f"Uploaded video file: {video_path}")
    
            # Wait for processing
            await asyncio.sleep(15)
    
            # Find caption/description field
            caption_field = await page.query_selector('div[contenteditable="true"]')
            if not caption_field:
                caption_field = await page.wait_for_selector(
                    'div[contenteditable="true"]', timeout=20000
                )
    
            if caption_field:
                # Clear existing text
                await caption_field.click()
                await page.keyboard.press("Control+a")
                await page.keyboard.press("Backspace")
                await asyncio.sleep(0.5)
    
                # Type caption
                await caption_field.type(caption, delay=random.randint(20, 50))
    
                # Add hashtags
                if hashtags:
                    await caption_field.type("\n\n", delay=50)
                    hashtag_text = " ".join(f"#{tag.lstrip('#')}" for tag in hashtags)
                    await caption_field.type(hashtag_text, delay=random.randint(20, 50))
    
                await asyncio.sleep(3)
    
            # Dismiss tutorial overlay if present
            try:
                overlay = await page.query_selector('[class*="joyride"], [class*="overlay"]')
                if overlay:
                    await overlay.click()
                    await asyncio.sleep(1)
            except:
                pass
    
            # Click Post button
            post_selectors = [
                'button:has-text("Post")',
                'button:has-text("Publier")',
                '[data-e2e="post"]',
                'button[class*="submit"]',
            ]
    
            post_btn = None
            for sel in post_selectors:
                try:
                    post_btn = await page.query_selector(sel)
                    if post_btn and await post_btn.is_visible() and await post_btn.is_enabled():
                        break
                    post_btn = None
                except:
                    continue
    
            if not post_btn:
                result["error"] = "Could not find Post button"
                return result
    
            await post_btn.click()
            await asyncio.sleep(5)
    
            # Handle "Continue to post?" dialog
            for _ in range(12):
                try:
                    post_now = await page.query_selector(
                        'button:has-text("Post now"), button:has-text("Publier maintenant")'
                    )
                    if post_now and await post_now.is_visible():
                        await post_now.click()
                        await asyncio.sleep(3)
                except:
                    pass
    
                # Check for success
                if "/content" in page.url:
                    result["success"] = True
                    result["message"] = "Video published successfully"
                    return result
    
                # Check for success message
                success_el = await page.query_selector(
                    ':has-text("Your video is"), :has-text("being uploaded")'
                )
                if success_el:
                    result["success"] = True
                    result["message"] = "Video upload in progress"
                    return result
    
                await asyncio.sleep(10)
    
            result["error"] = "Publish timed out — no confirmation received"
    
        except Exception as e:
            result["error"] = str(e)
    
        return result
  • The server-side handler that receives the 'tiktok_publish' tool call and executes the browser.publish_video() method.
    elif name == "tiktok_publish":
        result = await browser.publish_video(
            arguments["video_path"],
            arguments["caption"],
            arguments.get("hashtags", []),
        )
        return [TextContent(type="text", text=json.dumps(result, indent=2, ensure_ascii=False))]
  • Definition of the 'tiktok_publish' tool in the TOOLS list, including input schema.
    Tool(
        name="tiktok_publish",
        description="Publish a video to TikTok. Requires authenticated session. Uses browser automation via Creator Center.",
        inputSchema={
            "type": "object",
            "properties": {
                "video_path": {"type": "string", "description": "Path to the video file to upload"},
                "caption": {"type": "string", "description": "Video caption/description"},
                "hashtags": {
                    "type": "array",
                    "items": {"type": "string"},
                    "description": "List of hashtags (without #)",
                },
            },
            "required": ["video_path", "caption"],
        },
    ),
Behavior3/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It adds useful context beyond basic functionality by mentioning authentication requirements and the method ('Uses browser automation via Creator Center'), which hints at potential delays or UI interactions. However, it lacks details on error handling, rate limits, or what happens on success/failure, leaving gaps in transparency.

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

Conciseness5/5

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

The description is extremely concise and front-loaded, consisting of only two sentences that efficiently convey the core action, prerequisites, and implementation method. Every word earns its place with no redundancy or unnecessary details, making it easy to parse and understand quickly.

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 the complexity of a publish operation with no annotations and no output schema, the description is somewhat incomplete. It covers authentication and method but omits critical details like return values, error cases, or side effects (e.g., video visibility). For a mutation tool with zero structured metadata, more behavioral context would be needed for full completeness.

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?

Schema description coverage is 100%, so the input schema already fully documents the parameters (video_path, caption, hashtags). The description does not add any additional meaning or examples beyond what the schema provides, such as file format constraints for video_path or hashtag formatting tips. This meets the baseline for high schema coverage but offers no extra value.

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

Purpose5/5

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

The description clearly states the specific action ('Publish a video') and the target platform ('to TikTok'), distinguishing it from sibling tools like tiktok_download or tiktok_analyze_trend. It uses a precise verb ('Publish') and identifies the resource ('a video'), making the purpose unambiguous and distinct from alternatives.

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

Usage Guidelines4/5

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

The description provides clear context for when to use this tool by stating 'Requires authenticated session,' which implies it should be used only after authentication is established. However, it does not explicitly mention when not to use it or name specific alternatives among siblings, such as using tiktok_video_info for read-only operations instead.

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/follox42/tiktok-mcp'

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