Skip to main content
Glama
rhettlong

USCardForum MCP Server

by rhettlong

get_topic_posts

Fetch posts from USCardForum topics in batches for paginated reading, starting at a specified position to retrieve content, author details, and engagement metrics.

Instructions

Fetch a batch of posts from a topic starting at a specific position.

Args:
    topic_id: The numeric topic ID
    post_number: Which post number to start from (default: 1 = first post)
    include_raw: Include raw markdown source (default: False, returns HTML)

This fetches ~20 posts per call starting from post_number.
Use for paginated reading of topics.

Returns a list of Post objects with:
- post_number: Position in topic (1, 2, 3...)
- username: Author's username
- cooked: HTML content of the post
- raw: Markdown source (if include_raw=True)
- created_at: When posted
- updated_at: Last edit time
- like_count: Number of likes
- reply_count: Number of direct replies
- reply_to_post_number: Which post this replies to (if any)

Pagination example:
1. Call with post_number=1, get posts 1-20
2. Call with post_number=21, get posts 21-40
3. Continue until no posts returned

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
topic_idYesThe numeric topic ID
post_numberNoWhich post number to start from (default: 1 = first post)
include_rawNoInclude raw markdown source (default: False, returns HTML)

Implementation Reference

  • Primary MCP tool handler for 'get_topic_posts', decorated with @mcp.tool(). Defines input schema via Annotated[Field] and delegates to DiscourseClient.get_topic_posts.
    @mcp.tool()
    def get_topic_posts(
        topic_id: Annotated[
            int,
            Field(description="The numeric topic ID"),
        ],
        post_number: Annotated[
            int,
            Field(default=1, description="Which post number to start from (default: 1 = first post)"),
        ] = 1,
        include_raw: Annotated[
            bool,
            Field(default=False, description="Include raw markdown source (default: False, returns HTML)"),
        ] = False,
    ) -> list[Post]:
        """
        Fetch a batch of posts from a topic starting at a specific position.
    
        Args:
            topic_id: The numeric topic ID
            post_number: Which post number to start from (default: 1 = first post)
            include_raw: Include raw markdown source (default: False, returns HTML)
    
        This fetches ~20 posts per call starting from post_number.
        Use for paginated reading of topics.
    
        Returns a list of Post objects with:
        - post_number: Position in topic (1, 2, 3...)
        - username: Author's username
        - cooked: HTML content of the post
        - raw: Markdown source (if include_raw=True)
        - created_at: When posted
        - updated_at: Last edit time
        - like_count: Number of likes
        - reply_count: Number of direct replies
        - reply_to_post_number: Which post this replies to (if any)
    
        Pagination example:
        1. Call with post_number=1, get posts 1-20
        2. Call with post_number=21, get posts 21-40
        3. Continue until no posts returned
        """
        return get_client().get_topic_posts(
            topic_id, post_number=post_number, include_raw=include_raw
        )
  • Pydantic model defining the structure and validation for Post objects returned by get_topic_posts tool.
    class Post(BaseModel):
        """A single post within a topic."""
    
        id: int = Field(..., description="Unique post identifier")
        post_number: int = Field(..., description="Position in topic (1-indexed)")
        username: str = Field(..., description="Author's username")
        cooked: str | None = Field(None, description="HTML-rendered content")
        raw: str | None = Field(None, description="Raw markdown source")
        created_at: datetime | None = Field(None, description="When posted")
        updated_at: datetime | None = Field(None, description="Last edit time")
        like_count: int = Field(0, description="Number of likes")
        reply_count: int = Field(0, description="Number of direct replies")
        reply_to_post_number: int | None = Field(
            None, description="Post number this replies to"
        )
    
        class Config:
            extra = "ignore"
  • DiscourseClient method delegated to by the MCP tool handler, which forwards to TopicsAPI.
    def get_topic_posts(
        self,
        topic_id: int,
        *,
        post_number: int = 1,
        include_raw: bool = False,
    ) -> list[Post]:
        """Fetch a batch of posts starting at a specific post number.
    
        Args:
            topic_id: Topic ID
            post_number: Starting post number (default: 1)
            include_raw: Include raw markdown (default: False)
    
        Returns:
            List of posts sorted by post_number
        """
        return self._topics.get_topic_posts(
            topic_id, post_number=post_number, include_raw=include_raw
        )
  • TopicsAPI implementation that performs the HTTP request to Discourse API, parses JSON response, constructs Post models, and returns the list of posts.
    def get_topic_posts(
        self,
        topic_id: int,
        *,
        post_number: int = 1,
        include_raw: bool = False,
    ) -> list[Post]:
        """Fetch a batch of posts starting at a specific post number.
    
        Args:
            topic_id: Topic ID
            post_number: Starting post number (default: 1)
            include_raw: Include raw markdown (default: False)
    
        Returns:
            List of posts sorted by post_number
        """
        params_list: list[tuple[str, Any]] = [
            ("post_number", int(post_number)),
            ("asc", "true"),
            ("include_suggested", "false"),
            ("include_raw", str(include_raw).lower()),
        ]
        payload = self._get(f"t/topic/{int(topic_id)}.json", params=params_list)
        raw_posts = payload.get("post_stream", {}).get("posts", [])
    
        posts = []
        for p in raw_posts:
            post = Post(
                id=p.get("id", 0),
                post_number=p.get("post_number", 0),
                username=p.get("username", ""),
                cooked=p.get("cooked"),
                raw=p.get("raw") if include_raw else None,
                created_at=p.get("created_at"),
                updated_at=p.get("updated_at"),
                like_count=p.get("like_count", 0),
                reply_count=p.get("reply_count", 0),
                reply_to_post_number=p.get("reply_to_post_number"),
            )
            posts.append(post)
    
        posts.sort(key=lambda p: p.post_number)
        return posts
  • Shared helper function get_client() used by the MCP tool to obtain the singleton DiscourseClient instance (with optional auto-login).
    def get_client() -> DiscourseClient:
        """Get or create the Discourse client instance."""
        global _client, _login_attempted
    
        if _client is None:
            base_url = os.environ.get("USCARDFORUM_URL", "https://www.uscardforum.com")
            timeout = float(os.environ.get("USCARDFORUM_TIMEOUT", "15.0"))
            _client = DiscourseClient(base_url=base_url, timeout_seconds=timeout)
    
            # Auto-login if credentials are provided
            if not _login_attempted:
                _login_attempted = True
                username = os.environ.get("NITAN_USERNAME")
                password = os.environ.get("NITAN_PASSWORD")
    
                if username and password:
                    try:
                        result = _client.login(username, password)
                        if result.success:
                            print(f"[uscardforum] Auto-login successful as '{result.username}'")
                        elif result.requires_2fa:
                            print(
                                "[uscardforum] Auto-login failed: 2FA required. Use login() tool with second_factor_token."
                            )
                        else:
                            print(
                                f"[uscardforum] Auto-login failed: {result.error or 'Unknown error'}"
                            )
                    except Exception as e:  # pragma: no cover - logging side effect
                        print(f"[uscardforum] Auto-login error: {e}")
    
        return _client

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/rhettlong/uscardforum-mcp'

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