Skip to main content
Glama
raalarcon9705

raalarcon-jira-mcp-server

query_wiki

Retrieve Confluence wiki page content using its unique page code.

Instructions

Get Confluence page content by specific code (like F4CjNw).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYesPage code to search for (like F4CjNw)

Implementation Reference

  • The main handler function `handleWikiTool` that executes the 'query_wiki' tool logic. It takes a Confluence page code (like F4CjNw), follows redirects to get the page ID, fetches the page content via Confluence API, converts HTML to plain text, and returns the formatted result.
    export async function handleWikiTool(
      name: string,
      args: Record<string, unknown>
    ): Promise<Record<string, unknown>> {
      try {
        if (name !== 'query_wiki') {
          throw new Error(`Unknown wiki tool: ${name}`);
        }
    
        const { query } = args as {
          query: string;
        };
    
        const confluenceClient = new ConfluenceClient({
          host: process.env.JIRA_HOST!,
          authentication: {
            basic: {
              email: process.env.JIRA_EMAIL!,
              apiToken: process.env.JIRA_API_TOKEN!,
            },
          },
        });
        // For codes like F4CjNw, follow redirects to get the page ID
        let pageId = query;
    
        // If the query is a code like F4CjNw, follow redirects
        if (!/^\d+$/.test(query)) {
          try {
            // Follow redirects to get the page ID
            // eslint-disable-next-line no-undef
            const response = await fetch(`${process.env.JIRA_HOST}/wiki/x/${query}`, {
              method: 'GET',
              redirect: 'follow',
              headers: {
                'Authorization': `Basic ${Buffer.from(`${process.env.JIRA_EMAIL}:${process.env.JIRA_API_TOKEN}`).toString('base64')}`,
              },
            });
    
            if (!response.ok) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Failed to access page with code: ${query}. Status: ${response.status}`,
                  },
                ],
                isError: true,
              };
            }
    
            // Extract the page ID from the final URL
            const finalUrl = response.url;
            const pageIdMatch = finalUrl.match(/\/pages\/(\d+)/);
    
            if (!pageIdMatch) {
              return {
                content: [
                  {
                    type: 'text',
                    text: `Could not extract page ID from URL: ${finalUrl}`,
                  },
                ],
                isError: true,
              };
            }
    
            pageId = pageIdMatch[1];
          } catch (error) {
            return {
              content: [
                {
                  type: 'text',
                  text: `Error following redirects: ${error instanceof Error ? error.message : 'Unknown error'}`,
                },
              ],
              isError: true,
            };
          }
        }
    
        // Get the complete page content using the ID
        const page = await confluenceClient.content.getContentById({
          id: pageId,
          expand: ['body.storage', 'space', 'version', 'ancestors'],
        });
    
        // Convert HTML to plain text
        const plainTextBody = convertHtmlToPlainText(page.body?.storage?.value || '');
    
        return {
          content: [
            {
              type: 'text',
              text: `# ${page.title}
    
    **ID:** ${page.id}
    **Space:** ${page.space?.name} (${page.space?.key})
    **URL:** ${page._links?.webui}
    **Author:** ${page.version?.by?.displayName}
    **Last Modified:** ${page.version?.when}
    **Code:** ${query}
    
    ## Content
    
    ${plainTextBody}
    
    ## Page Hierarchy
    
    ${page.ancestors?.map((ancestor: Models.Content, index: number) => `${'  '.repeat(index)}- ${ancestor.title} (${ancestor.type})`).join('\n') || 'No hierarchy available'}`,
            },
          ],
        };
      } catch (error) {
        let errorMessage = 'Unknown error occurred';
        let errorType = 'Unknown';
    
        if (error instanceof Error) {
          errorMessage = error.message;
    
          // Categorize different types of errors for better debugging
          if (error.message.includes('401') || error.message.includes('Unauthorized')) {
            errorType = 'Authentication Error';
            errorMessage = 'Authentication failed. Please check your JIRA_EMAIL and JIRA_API_TOKEN credentials.';
          } else if (error.message.includes('403') || error.message.includes('Forbidden')) {
            errorType = 'Permission Error';
            errorMessage = 'Access denied. You may not have permission to access this Confluence space or content.';
          } else if (error.message.includes('404') || error.message.includes('Not Found')) {
            errorType = 'Not Found Error';
            errorMessage = 'Content not found. The page, space, or query may not exist or may not be accessible.';
          } else if (error.message.includes('429') || error.message.includes('Rate Limit')) {
            errorType = 'Rate Limit Error';
            errorMessage = 'Too many requests. Please wait before making another request.';
          } else if (error.message.includes('500') || error.message.includes('Internal Server Error')) {
            errorType = 'Server Error';
            errorMessage = 'Confluence server error. Please try again later.';
          } else if (error.message.includes('CQL') || error.message.includes('query')) {
            errorType = 'Query Error';
            errorMessage = 'Invalid search query. Please check your search parameters.';
          } else if (error.message.includes('network') || error.message.includes('timeout')) {
            errorType = 'Network Error';
            errorMessage = 'Network connection failed. Please check your internet connection and try again.';
          } else if (error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) {
            errorType = 'Connection Error';
            errorMessage = 'Cannot connect to Confluence server. Please check the host URL and network connectivity.';
          }
        }
    
        return {
          content: [
            {
              type: 'text',
              text: `Error in wiki operation (${errorType}): ${errorMessage}`,
            },
          ],
          isError: true,
        };
      }
    }
  • The tool definition including inputSchema for 'query_wiki'. Defines it as a tool with a required 'query' string parameter (page code like F4CjNw).
    export function createWikiTools(): Tool[] {
      return [
        {
          name: 'query_wiki',
          description: 'Get Confluence page content by specific code (like F4CjNw).',
          inputSchema: {
            type: 'object',
            properties: {
              query: {
                type: 'string',
                description: 'Page code to search for (like F4CjNw)',
              },
            },
            required: ['query'],
          },
        },
  • src/index.ts:48-60 (registration)
    Registration of 'query_wiki' tool listing: calls `createWikiTools()` to include the tool in the list of available tools returned by the server.
    const wikiTools = createWikiTools();
    
    return {
      tools: [
        ...projectTools,
        ...issueTools,
        ...commentTools,
        ...transitionTools,
        ...assignmentTools,
        ...sprintTools,
        ...wikiTools,
      ],
    };
  • src/index.ts:107-108 (registration)
    Routing/dispatch for 'query_wiki' tool calls: when a tool call name starts with 'query_wiki', it delegates to `handleWikiTool()`.
    } else if (name.startsWith('query_wiki')) {
      return await handleWikiTool(name, args || {});
  • Helper function `convertHtmlToPlainText` that strips HTML tags and converts HTML entities to plain text, used by the handler to process Confluence page content.
    function convertHtmlToPlainText(html: string): string {
      if (!html) return '';
    
      try {
        // Remove HTML tags and convert HTML entities preserving line breaks
        let text = html
          .replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '') // Remove scripts
          .replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '') // Remove styles
          .replace(/<br\s*\/?>/gi, '\n') // Convert <br> to line breaks
          .replace(/<\/p>/gi, '\n\n') // Convert </p> to double line breaks
          .replace(/<\/div>/gi, '\n') // Convert </div> to line break
          .replace(/<\/h[1-6]>/gi, '\n\n') // Convert </h1>-</h6> to double line breaks
          .replace(/<\/li>/gi, '\n') // Convert </li> to line break
          .replace(/<\/tr>/gi, '\n') // Convert </tr> to line break
          .replace(/<[^>]*>/g, '') // Remove all other HTML tags
          .replace(/ /g, ' ') // Convert non-breaking spaces
          .replace(/&/g, '&') // Convert ampersands
          .replace(/</g, '<') // Convert less than
          .replace(/>/g, '>') // Convert greater than
          .replace(/"/g, '"') // Convert quotes
          .replace(/'/g, '\'') // Convert apostrophes
          .replace(/[ \t]+/g, ' ') // Normalize spaces and tabs (but not line breaks)
          .replace(/\n\s*\n\s*\n/g, '\n\n') // Reduce multiple consecutive line breaks
          .trim();
    
        return text;
      } catch (_error) {
        return html; // If there's an error, return the original HTML
      }
    }
Behavior2/5

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

No annotations are provided, so the description bears full responsibility for behavioral disclosure. It merely states 'Get', implying a read operation, but omits details about side effects, authentication requirements, rate limits, or error handling. This leaves significant gaps.

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?

Single sentence, front-loaded with key information, no redundant words. Highly efficient.

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?

For a simple tool with one parameter and no output schema, the description is minimally adequate. It explains how to use the tool but omits what the response contains (full page, snippet, metadata?) and any limitations. Additional context would be beneficial.

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%, and the parameter description in both tool and schema is identical. The description adds the example 'F4CjNw', which aids understanding, but does not elaborate on format, case sensitivity, or behavior when code is invalid. Baseline 3 is appropriate.

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 (Get) and resource (Confluence page content) with a specific identifier (code like F4CjNw). It distinguishes from sibling JIRA tools by its focus on Confluence, though the term 'code' could be interpreted as a page ID or short link.

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?

No guidance on when to use this tool versus alternatives, prerequisites, or context. The siblings are all JIRA-related, so the domain difference is evident, but the description does not explicitly address usage scenarios or exclusions.

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/raalarcon9705/jira-mcp'

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