Skip to main content
Glama

wp_seo_suggest_internal_links

Analyze WordPress content to identify relevant internal linking opportunities that improve SEO performance by connecting related posts and pages.

Instructions

Analyze content and suggest relevant internal linking opportunities for better SEO

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
siteNoSite identifier for multi-site setups
postIdYesWordPress post ID to analyze for linking opportunities
maxSuggestionsNoMaximum number of link suggestions (default: 5)
minimumRelevanceNoMinimum relevance score (0-100) for suggestions

Implementation Reference

  • MCP handler function for the wp_seo_suggest_internal_links tool. Processes input arguments, constructs parameters, and delegates to SEOTools.suggestInternalLinks.
    export async function handleSuggestInternalLinks(
      client: WordPressClient,
      args: Record<string, unknown>,
    ): Promise<unknown> {
      const logger = LoggerFactory.tool("wp_seo_suggest_internal_links");
    
      try {
        const seoTools = getSEOToolsInstance();
        const params: SEOToolParams = {
          postId: args.postId as number,
          site: args.site as string,
        };
    
        // Add optional parameters
        if (args.maxSuggestions) {
          params.maxSuggestions = args.maxSuggestions as number;
        }
        if (args.minimumRelevance) {
          params.minimumRelevance = args.minimumRelevance as number;
        }
    
        return await seoTools.suggestInternalLinks(params);
      } catch (error) {
        logger.error("Failed to suggest internal links", { error, args });
        throw error;
      }
    }
  • Core implementation of internal linking suggestion generation, including content analysis, candidate post selection, relevance scoring, filtering, ranking, and contextual placement enhancement.
    async generateSuggestions(sourcePost: WordPressPost, params: SEOToolParams): Promise<InternalLinkSuggestion[]> {
      this.logger.debug("Generating internal linking suggestions", {
        postId: sourcePost.id,
        title: sourcePost.title?.rendered?.substring(0, 50),
        maxSuggestions: this.config.maxSuggestions,
      });
    
      try {
        // Analyze source post content
        const sourceAnalysis = await this.analyzePostContent(sourcePost);
    
        // Get candidate posts for linking
        const candidatePosts = await this.getCandidatePosts(sourcePost, params);
    
        // Analyze candidate posts
        const candidateAnalyses = await Promise.all(candidatePosts.map((post) => this.analyzePostContent(post)));
    
        // Calculate relevance scores
        const scoredSuggestions = this.calculateRelevanceScores(
          sourcePost,
          sourceAnalysis,
          candidatePosts,
          candidateAnalyses,
        );
    
        // Filter and rank suggestions
        const filteredSuggestions = scoredSuggestions
          .filter((suggestion) => suggestion.relevance >= this.config.minRelevanceScore)
          .sort((a, b) => b.relevance - a.relevance)
          .slice(0, this.config.maxSuggestions);
    
        // Add contextual placement information
        const enhancedSuggestions = await Promise.all(
          filteredSuggestions.map((suggestion) => this.enhanceWithContextualPlacement(sourcePost, suggestion)),
        );
    
        this.logger.info("Generated internal linking suggestions", {
          sourcePostId: sourcePost.id,
          candidatesAnalyzed: candidatePosts.length,
          suggestionsFound: enhancedSuggestions.length,
          avgRelevanceScore:
            enhancedSuggestions.length > 0
              ? (enhancedSuggestions.reduce((sum, s) => sum + s.relevance, 0) / enhancedSuggestions.length).toFixed(1)
              : 0,
        });
    
        return enhancedSuggestions;
      } catch (_error) {
        this.logger.error("Failed to generate internal linking suggestions", {
          postId: sourcePost.id,
          _error: _error instanceof Error ? _error.message : String(_error),
        });
        throw _error;
      }
    }
  • Tool schema definition for wp_seo_suggest_internal_links, including input schema with postId (required), maxSuggestions, minimumRelevance, and site.
    export const suggestInternalLinksTool: Tool = {
      name: "wp_seo_suggest_internal_links",
      description: "Analyze content and suggest relevant internal linking opportunities for better SEO",
      inputSchema: {
        type: "object",
        properties: {
          postId: {
            type: "number",
            description: "WordPress post ID to analyze for linking opportunities",
          },
          maxSuggestions: {
            type: "number",
            description: "Maximum number of link suggestions (default: 5)",
          },
          minimumRelevance: {
            type: "number",
            description: "Minimum relevance score (0-100) for suggestions",
          },
          site: {
            type: "string",
            description: "Site identifier for multi-site setups",
          },
        },
        required: ["postId"],
      },
    };
  • Tool handler registration mapping in SEOTools.getHandlerForTool method, where 'wp_seo_suggest_internal_links' is mapped to the handleSuggestInternalLinks function. Used by getTools() for MCP registration.
    private getHandlerForTool(toolName: string): unknown {
      const handlers: Record<string, unknown> = {
        wp_seo_analyze_content: handleAnalyzeContent,
        wp_seo_generate_metadata: handleGenerateMetadata,
        wp_seo_bulk_update_metadata: handleBulkUpdateMetadata,
        wp_seo_generate_schema: handleGenerateSchema,
        wp_seo_validate_schema: handleValidateSchema,
        wp_seo_suggest_internal_links: handleSuggestInternalLinks,
        wp_seo_site_audit: handlePerformSiteAudit,
        wp_seo_track_serp: handleTrackSERPPositions,
        wp_seo_keyword_research: handleKeywordResearch,
        wp_seo_test_integration: handleTestSEOIntegration,
        wp_seo_get_live_data: handleGetLiveSEOData,
      };
    
      return (
        handlers[toolName] ||
        (() => {
          throw new Error(`Unknown SEO tool: ${toolName}`);
        })
      );
    }
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It states the tool analyzes and suggests, implying a read-only operation, but doesn't clarify if it performs mutations, requires specific permissions, has rate limits, or what the output format looks like. For an analysis tool with zero annotation coverage, this leaves significant gaps in understanding its behavior.

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 a single, efficient sentence that front-loads the core action ('Analyze content and suggest relevant internal linking opportunities') and purpose ('for better SEO'). There is no wasted language, and it directly communicates the tool's function without unnecessary elaboration.

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 tool's moderate complexity (analysis with 4 parameters), no annotations, and no output schema, the description is minimally adequate. It covers the basic purpose but lacks details on behavioral traits, output format, and usage context. Without annotations or output schema, more completeness would be beneficial, but it meets a baseline level for understanding what the tool does.

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 schema already documents all four parameters (site, postId, maxSuggestions, minimumRelevance) with descriptions. The tool description adds no additional parameter semantics beyond what the schema provides, such as explaining how 'minimumRelevance' is calculated or typical use cases for 'site'. Baseline 3 is appropriate when the schema does the heavy lifting.

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 tool's purpose: 'Analyze content and suggest relevant internal linking opportunities for better SEO.' It specifies the verb ('analyze and suggest'), resource ('content'), and goal ('for better SEO'), making it distinct from most sibling tools. However, it doesn't explicitly differentiate from wp_seo_analyze_content, which might have overlapping analysis functions.

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. It doesn't mention prerequisites (e.g., needing a post to analyze), exclusions, or comparisons with sibling tools like wp_seo_analyze_content or wp_search_site, which might offer related functionality. Usage is implied through the action but not explicitly defined.

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

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