Skip to main content
Glama

wp_list_posts

Retrieve and filter WordPress posts using search terms, status, categories, or tags to manage and organize site content efficiently.

Instructions

Lists posts from a WordPress site with comprehensive filtering options. Supports search, status filtering, and category/tag filtering with enhanced metadata display.

Usage Examples: • Basic listing: wp_list_posts • Search posts: wp_list_posts --search="AI trends" • Filter by status: wp_list_posts --status="draft" • Category filtering: wp_list_posts --categories=[1,2,3] • Paginated results: wp_list_posts --per_page=20 --page=2 • Combined filters: wp_list_posts --search="WordPress" --status="publish" --per_page=10

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
siteNoThe ID of the WordPress site to target (from mcp-wordpress.config.json). Required if multiple sites are configured.
per_pageNoNumber of items to return per page (max 100).
searchNoLimit results to those matching a search term.
statusNoFilter by post status.
categoriesNoLimit results to posts in specific category IDs.
tagsNoLimit results to posts with specific tag IDs.

Implementation Reference

  • Core handler function that validates input parameters, sanitizes data, fetches posts using WordPressClient.getPosts(), enriches with author/category/tag info, handles streaming for large sets, and formats a rich markdown response with metadata and pagination tips.
    export async function handleListPosts(
      client: WordPressClient,
      params: PostQueryParams,
    ): Promise<WordPressPost[] | string> {
      try {
        // Handle null/undefined parameters
        if (!params || typeof params !== "object") {
          throw ErrorHandlers.validationError("params", params, "valid object");
        }
    
        // Enhanced input validation and sanitization
        const paginationValidated = validatePaginationParams(params);
    
        const sanitizedParams = {
          ...params,
          ...paginationValidated,
        };
    
        // Validate and sanitize search term
        if (sanitizedParams.search) {
          sanitizedParams.search = sanitizedParams.search.trim();
          if (sanitizedParams.search.length === 0) {
            delete sanitizedParams.search;
          }
        }
    
        // Validate category and tag IDs if provided
        if (sanitizedParams.categories) {
          sanitizedParams.categories = sanitizedParams.categories.map((id) => validateId(id, "category ID"));
        }
    
        if (sanitizedParams.tags) {
          sanitizedParams.tags = sanitizedParams.tags.map((id) => validateId(id, "tag ID"));
        }
    
        // Validate and normalize status parameter to array (WordPress REST API expects array)
        if (sanitizedParams.status) {
          const validStatuses = ["publish", "future", "draft", "pending", "private"];
          const statusesToCheck = Array.isArray(sanitizedParams.status) ? sanitizedParams.status : [sanitizedParams.status];
    
          for (const statusToCheck of statusesToCheck) {
            if (!validStatuses.includes(statusToCheck)) {
              throw ErrorHandlers.validationError("status", statusToCheck, "one of: " + validStatuses.join(", "));
            }
          }
    
          // Normalize to array format as expected by WordPress REST API
          sanitizedParams.status = statusesToCheck as PostStatus[];
        }
    
        // Performance optimization: set reasonable defaults
        if (!sanitizedParams.per_page) {
          sanitizedParams.per_page = 10; // Default to 10 posts for better performance
        }
    
        const posts = await client.getPosts(sanitizedParams);
        if (posts.length === 0) {
          const searchInfo = sanitizedParams.search ? ` matching "${sanitizedParams.search}"` : "";
          const statusInfo = sanitizedParams.status ? ` with status "${sanitizedParams.status}"` : "";
          return `No posts found${searchInfo}${statusInfo}. Try adjusting your search criteria or check if posts exist.`;
        }
    
        // Use streaming for large result sets (>50 posts)
        if (posts.length > 50) {
          const streamResults: StreamingResult<unknown>[] = [];
    
          for await (const result of WordPressDataStreamer.streamPosts(posts, {
            includeAuthor: true,
            includeCategories: true,
            includeTags: true,
            batchSize: 20,
          })) {
            streamResults.push(result);
          }
    
          return StreamingUtils.formatStreamingResponse(streamResults, "posts");
        }
    
        // Add comprehensive site context information
        const siteUrl = client.getSiteUrl ? client.getSiteUrl() : "Unknown site";
        const totalPosts = posts.length;
        const statusCounts = posts.reduce(
          (acc, p) => {
            acc[p.status] = (acc[p.status] || 0) + 1;
            return acc;
          },
          {} as Record<string, number>,
        );
    
        // Enhanced metadata
        const metadata = [
          `📊 **Posts Summary**: ${totalPosts} total`,
          `📝 **Status Breakdown**: ${Object.entries(statusCounts)
            .map(([status, count]) => `${status}: ${count}`)
            .join(", ")}`,
          `🌐 **Source**: ${siteUrl}`,
          `📅 **Retrieved**: ${new Date().toLocaleString()}`,
          ...(params.search ? [`🔍 **Search Term**: "${params.search}"`] : []),
          ...(params.categories ? [`📁 **Categories**: ${params.categories.join(", ")}`] : []),
          ...(params.tags ? [`🏷️ **Tags**: ${params.tags.join(", ")}`] : []),
        ];
    
        // Fetch additional metadata for enhanced responses
        const authorIds = [...new Set(posts.map((p) => p.author).filter(Boolean))];
        const categoryIds = [...new Set(posts.flatMap((p) => p.categories || []))];
        const tagIds = [...new Set(posts.flatMap((p) => p.tags || []))];
    
        // Fetch authors, categories, and tags in parallel for better performance
        const [authors, categories, tags] = await Promise.all([
          authorIds.length > 0
            ? Promise.all(
                authorIds.map(async (id) => {
                  try {
                    const user = await client.getUser(id);
                    return { id, name: user.name || user.username || `User ${id}` };
                  } catch {
                    return { id, name: `User ${id}` };
                  }
                }),
              )
            : [],
          categoryIds.length > 0
            ? Promise.all(
                categoryIds.map(async (id) => {
                  try {
                    const category = await client.getCategory(id);
                    return { id, name: category.name || `Category ${id}` };
                  } catch {
                    return { id, name: `Category ${id}` };
                  }
                }),
              )
            : [],
          tagIds.length > 0
            ? Promise.all(
                tagIds.map(async (id) => {
                  try {
                    const tag = await client.getTag(id);
                    return { id, name: tag.name || `Tag ${id}` };
                  } catch {
                    return { id, name: `Tag ${id}` };
                  }
                }),
              )
            : [],
        ]);
    
        // Create lookup maps for performance
        const authorMap = new Map(authors.map((a) => [a.id, a.name]));
        const categoryMap = new Map(categories.map((c) => [c.id, c.name]));
        const tagMap = new Map(tags.map((t) => [t.id, t.name]));
    
        const content =
          metadata.join("\n") +
          "\n\n" +
          posts
            .map((p) => {
              const date = new Date(p.date);
              const formattedDate = date.toLocaleDateString("en-US", {
                year: "numeric",
                month: "short",
                day: "numeric",
              });
              const excerpt = p.excerpt?.rendered ? sanitizeHtml(p.excerpt.rendered).substring(0, 80) + "..." : "";
    
              // Enhanced metadata
              const authorName = authorMap.get(p.author) || `User ${p.author}`;
              const postCategories = (p.categories || []).map((id) => categoryMap.get(id) || `Category ${id}`);
              const postTags = (p.tags || []).map((id) => tagMap.get(id) || `Tag ${id}`);
    
              let postInfo = `- ID ${p.id}: **${p.title.rendered}** (${p.status})\n`;
              postInfo += `  👤 Author: ${authorName}\n`;
              postInfo += `  📅 Published: ${formattedDate}\n`;
              if (postCategories.length > 0) {
                postInfo += `  📁 Categories: ${postCategories.join(", ")}\n`;
              }
              if (postTags.length > 0) {
                postInfo += `  🏷️ Tags: ${postTags.join(", ")}\n`;
              }
              if (excerpt) {
                postInfo += `  📝 Excerpt: ${excerpt}\n`;
              }
              postInfo += `  🔗 Link: ${p.link}`;
    
              return postInfo;
            })
            .join("\n\n");
    
        // Add pagination guidance for large result sets
        let finalContent = content;
        if (posts.length >= (sanitizedParams.per_page || 10)) {
          finalContent += `\n\n📄 **Pagination Tip**: Use \`per_page\` parameter to control results (max 100). Current: ${
            sanitizedParams.per_page || 10
          }`;
        }
    
        return finalContent;
      } catch (_error) {
        throw new Error(`Failed to list posts: ${getErrorMessage(_error)}`);
      }
    }
  • MCP tool schema definition for wp_list_posts including name, detailed description with usage examples, and inputSchema with properties for per_page, search, status, categories, tags.
    export const listPostsTool: MCPTool = {
      name: "wp_list_posts",
      description:
        "Lists posts from a WordPress site with comprehensive filtering options. Supports search, status filtering, and category/tag filtering with enhanced metadata display.\n\n" +
        "**Usage Examples:**\n" +
        "• Basic listing: `wp_list_posts`\n" +
        '• Search posts: `wp_list_posts --search="AI trends"`\n' +
        '• Filter by status: `wp_list_posts --status="draft"`\n' +
        "• Category filtering: `wp_list_posts --categories=[1,2,3]`\n" +
        "• Paginated results: `wp_list_posts --per_page=20 --page=2`\n" +
        '• Combined filters: `wp_list_posts --search="WordPress" --status="publish" --per_page=10`',
      inputSchema: {
        type: "object",
        properties: {
          per_page: {
            type: "number",
            description: "Number of items to return per page (max 100).",
          },
          search: {
            type: "string",
            description: "Limit results to those matching a search term.",
          },
          status: {
            type: "string",
            description: "Filter by post status.",
            enum: ["publish", "future", "draft", "pending", "private"],
          },
          categories: {
            type: "array",
            items: { type: "number" },
            description: "Limit results to posts in specific category IDs.",
          },
          tags: {
            type: "array",
            items: { type: "number" },
            description: "Limit results to posts with specific tag IDs.",
          },
        },
      },
    };
  • PostTools.getTools() maps tool definitions to include bound handlers via getHandlerForTool switch which binds wp_list_posts to this.handleListPosts.
    public getTools(): unknown[] {
      return postToolDefinitions.map((toolDef) => ({
        ...toolDef,
        handler: this.getHandlerForTool(toolDef.name),
      }));
    }
    
    /**
     * Maps tool names to their corresponding handler methods.
     *
     * This method provides the binding between tool definitions and their
     * implementations, ensuring proper context and error handling.
     *
     * @param toolName - The name of the tool to get a handler for
     * @returns The bound handler method for the specified tool
     * @private
     */
    private getHandlerForTool(toolName: string) {
      switch (toolName) {
        case "wp_list_posts":
          return this.handleListPosts.bind(this);
        case "wp_get_post":
          return this.handleGetPost.bind(this);
        case "wp_create_post":
          return this.handleCreatePost.bind(this);
        case "wp_update_post":
          return this.handleUpdatePost.bind(this);
        case "wp_delete_post":
          return this.handleDeletePost.bind(this);
        case "wp_get_post_revisions":
          return this.handleGetPostRevisions.bind(this);
        default:
          throw new Error(`Unknown tool: ${toolName}`);
      }
    }
  • Wrapper handler in PostTools class that extracts and normalizes MCP input parameters before delegating to core handleListPosts from PostHandlers.
    public async handleListPosts(
      client: WordPressClient,
      params: PostQueryParams | Record<string, unknown>,
    ): Promise<WordPressPost[] | string> {
      // Handle null/undefined params
      if (!params) {
        params = {};
      }
    
      // Extract only the relevant query parameters, excluding MCP-specific fields
      const queryParams: PostQueryParams = {};
    
      if (params.page !== undefined) queryParams.page = params.page as number;
      if (params.per_page !== undefined) queryParams.per_page = params.per_page as number;
      if (params.search !== undefined) queryParams.search = params.search as string;
      if (params.orderby !== undefined) queryParams.orderby = params.orderby as string;
      if (params.order !== undefined) queryParams.order = params.order as "asc" | "desc";
      if (params.status !== undefined) {
        // Handle both string and array forms
        const statusValue = params.status;
        if (Array.isArray(statusValue)) {
          queryParams.status = statusValue as PostStatus[];
        } else {
          queryParams.status = [statusValue as PostStatus];
        }
      }
      if (params.categories !== undefined) queryParams.categories = params.categories as number[];
      if (params.tags !== undefined) queryParams.tags = params.tags as number[];
      if (params.offset !== undefined) queryParams.offset = params.offset as number;
    
      return handleListPosts(client, queryParams);
    }

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/docdyhr/mcp-wordpress'

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