<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Angus</title>
    <description>The latest articles on DEV Community by Angus (@anguske).</description>
    <link>https://dev.to/anguske</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2980463%2F70cde874-6a1b-48cf-a484-1a40a8325855.png</url>
      <title>DEV Community: Angus</title>
      <link>https://dev.to/anguske</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anguske"/>
    <language>en</language>
    <item>
      <title>Frontend MCP Tool Development Tutorial</title>
      <dc:creator>Angus</dc:creator>
      <pubDate>Thu, 27 Mar 2025 06:01:57 +0000</pubDate>
      <link>https://dev.to/anguske/frontend-mcp-tool-development-tutorial-1pb4</link>
      <guid>https://dev.to/anguske/frontend-mcp-tool-development-tutorial-1pb4</guid>
      <description>&lt;h1&gt;
  
  
  MCP (Model Context Protocol) Tool Development Tutorial
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Core Concepts&lt;/li&gt;
&lt;li&gt;Usage Examples&lt;/li&gt;
&lt;li&gt;Best Practices&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) is a protocol for building interactions between AI models and external tools. It allows us to create custom tools and resources that enable AI models to perform specific tasks.&lt;br&gt;
MCP tool development uses the modelcontextprotocol SDK, GitHub repository: &lt;br&gt;
&lt;a href="https://github.com/modelcontextprotocol" rel="noopener noreferrer"&gt;https://github.com/modelcontextprotocol&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Core Concepts
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. StdioServerTransport
&lt;/h3&gt;

&lt;p&gt;StdioServerTransport is the transport layer of MCP, which implements communication between server and client through standard input/output (stdin/stdout).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio";

const transport = new StdioServerTransport();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. McpServer
&lt;/h3&gt;

&lt;p&gt;McpServer is the core server class of MCP, used to create and manage tools and resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { McpServer } from "@modelcontextprotocol/sdk/server/mcp";

const server = new McpServer({
  name: "your-server-name",
  description: "Server description",
  version: "1.0.0"
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Tool
&lt;/h3&gt;

&lt;p&gt;Tool is a function in MCP used to execute specific tasks. Each tool requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;Parameter schema (using Zod for validation)&lt;/li&gt;
&lt;li&gt;Execution function
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { z } from "zod";

server.tool(
  "tool-name",
  {
    param1: z.string(),
    param2: z.number().optional()
  },
  async (params) =&amp;gt; {
    // Tool implementation
    return {
      content: [{
        type: "text",
        text: "Execution result"
      }]
    };
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Resource
&lt;/h3&gt;

&lt;p&gt;Resource is used to manage accessible data or state. Each resource requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Name&lt;/li&gt;
&lt;li&gt;URI template&lt;/li&gt;
&lt;li&gt;Function to get content
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp";

server.resource(
  "resource-name",
  new ResourceTemplate("resource://{id}", { list: undefined }),
  async (uri, { id }) =&amp;gt; ({
    contents: [{
      uri: uri.href,
      text: `Resource content: ${id}`
    }]
  })
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Usage Examples
&lt;/h2&gt;

&lt;p&gt;Here we use the modelcontextprotocol typescript-sdk for development&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Dependencies
&lt;/h3&gt;

&lt;p&gt;First, install the necessary npm packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @modelcontextprotocol/sdk @playwright/test pngjs pixelmatch zod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let me demonstrate these concepts working together through a practical Playwright UI testing tool. This example will show how to create a complete visual testing tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Type Definitions
&lt;/h3&gt;

&lt;p&gt;First, we need to define the types for test configuration and results:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// types.ts
export interface VisualTestConfig {
  url: string;
  selector?: string;
  waitForSelector?: string;
  waitForTimeout?: number;
  threshold?: number;
  ignoreSelectors?: string[];
  viewport?: {
    width: number;
    height: number;
  };
  baselineImagePath?: string;
  baselineImage?: string | Buffer;
  login?: {
    url: string;
    usernameSelector: string;
    passwordSelector: string;
    submitSelector: string;
    username: string;
    password: string;
    successSelector?: string;
  };
  autoLogin?: {
    username: string;
    password: string;
    usernameSelector: string;
    passwordSelector: string;
    submitSelector: string;
    successSelector?: string;
    loginUrlPattern?: string;
  };
}

export interface VisualTestResult {
  success: boolean;
  message?: string;
  error?: string;
  diffPixels?: number;
  threshold?: number;
  passed?: boolean;
  baselineCreated?: boolean;
  baselineUpdated?: boolean;
  screenshots?: {
    current?: string;
    diff?: string;
  };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create Server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { McpServer } from "@modelcontextprotocol/sdk/server/mcp";

const server = new McpServer({
  name: "visual-test",
  description: "UI visual comparison test tool",
  version: "1.0.0"
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Implement Core Functions
&lt;/h3&gt;

&lt;h4&gt;
  
  
  3.1 Login Function
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function login(page: any, loginConfig: VisualTestConfig["login"]) {
  if (!loginConfig) return;

  await page.goto(loginConfig.url);
  await page.waitForSelector(loginConfig.usernameSelector);
  await page.waitForSelector(loginConfig.passwordSelector);
  await page.waitForSelector(loginConfig.submitSelector);

  await page.fill(loginConfig.usernameSelector, loginConfig.username);
  await page.fill(loginConfig.passwordSelector, loginConfig.password);
  await page.click(loginConfig.submitSelector);

  if (loginConfig.successSelector) {
    await page.waitForSelector(loginConfig.successSelector);
  } else {
    await page.waitForNavigation();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.2 Auto Login Function
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function autoLogin(page: any, config: VisualTestConfig["autoLogin"]) {
  if (!config) return false;

  try {
    await page.waitForSelector(config.usernameSelector);
    await page.waitForSelector(config.passwordSelector);
    await page.waitForSelector(config.submitSelector);

    await page.fill(config.usernameSelector, config.username);
    await page.fill(config.passwordSelector, config.password);
    await page.click(config.submitSelector);

    if (config.successSelector) {
      await page.waitForSelector(config.successSelector);
    } else {
      await page.waitForNavigation();
    }

    return true;
  } catch (error) {
    console.error("Auto login failed:", error);
    return false;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  3.3 Visual Test Core Function
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async function runVisualTest(config: VisualTestConfig): Promise&amp;lt;VisualTestResult&amp;gt; {
  const browser = await chromium.launch();
  const page = await browser.newPage();

  try {
    // Set viewport size
    const defaultViewport = { width: 1280, height: 720 };
    await page.setViewportSize({
      width: config.viewport?.width ?? defaultViewport.width,
      height: config.viewport?.height ?? defaultViewport.height
    });

    // Handle login
    if (config.login) {
      await login(page, config.login);
    }

    // Visit target page
    await page.goto(config.url);
    await page.waitForLoadState("networkidle");

    // Check if auto login is needed
    await checkLoginRedirect(page, config);

    // Wait for specified element
    if (config.waitForSelector) {
      await page.waitForSelector(config.waitForSelector);
    }

    // Wait for specified time
    if (config.waitForTimeout) {
      await page.waitForTimeout(config.waitForTimeout);
    }

    // Hide elements to ignore
    if (config.ignoreSelectors?.length) {
      await page.evaluate((selectors) =&amp;gt; {
        selectors.forEach((selector) =&amp;gt; {
          const elements = document.querySelectorAll(selector);
          elements.forEach((el) =&amp;gt; {
            (el as HTMLElement).style.visibility = "hidden";
          });
        });
      }, config.ignoreSelectors);
    }

    // Get page screenshot
    const screenshot = await page.screenshot({
      fullPage: !config.selector,
      type: "png",
      ...(config.selector ? { selector: config.selector } : {}),
    });

    // Save current screenshot
    const currentScreenshotPath = path.join(screenshotsDir, "current.png");
    fs.writeFileSync(currentScreenshotPath, screenshot);

    // Handle baseline image
    const baselineScreenshotPath = path.join(screenshotsDir, "baseline.png");
    if (fs.existsSync(baselineScreenshotPath)) {
      console.log("Using existing baseline image");
    } else if (config.baselineImagePath) {
      const baselineBuffer = fs.readFileSync(config.baselineImagePath);
      fs.writeFileSync(baselineScreenshotPath, baselineBuffer);
    } else if (config.baselineImage) {
      let baselineBuffer: Buffer;
      if (Buffer.isBuffer(config.baselineImage)) {
        baselineBuffer = config.baselineImage;
      } else {
        baselineBuffer = Buffer.from(config.baselineImage, "base64");
      }
      fs.writeFileSync(baselineScreenshotPath, baselineBuffer);
    } else {
      fs.copyFileSync(currentScreenshotPath, baselineScreenshotPath);
      return {
        success: true,
        message: "Created new baseline screenshot",
        baselineCreated: true,
      };
    }

    // Image comparison
    const baseline = PNG.sync.read(fs.readFileSync(baselineScreenshotPath));
    const current = PNG.sync.read(screenshot);

    if (baseline.width !== current.width || baseline.height !== current.height) {
      fs.copyFileSync(currentScreenshotPath, baselineScreenshotPath);
      return {
        success: true,
        message: "Updated baseline screenshot",
        baselineUpdated: true,
      };
    }

    // Create diff image
    const { width, height } = baseline;
    const diff = new PNG({ width, height });
    const numDiffPixels = pixelmatch(
      baseline.data,
      current.data,
      diff.data,
      width,
      height,
      { threshold: config.threshold ? config.threshold / 100 : 0.1 }
    );

    // Save diff image
    fs.writeFileSync(
      path.join(screenshotsDir, "diff.png"),
      PNG.sync.write(diff)
    );

    return {
      success: true,
      message: "Successfully created diff image",
      diffPixels: numDiffPixels,
      threshold: config.threshold || 100,
      passed: numDiffPixels &amp;lt; (config.threshold || 100),
    };
  } catch (error) {
    console.error("Visual comparison test failed:", error);
    return {
      success: false,
      error: error instanceof Error ? error.message : "Unknown error",
    };
  } finally {
    await browser.close();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Define MCP Tool
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;server.tool(
  "playwright-ui-test",
  {
    url: z.string(),
    selector: z.string().optional(),
    waitForSelector: z.string().optional(),
    waitForTimeout: z.number().optional(),
    threshold: z.number().optional(),
    ignoreSelectors: z.array(z.string()).optional(),
    viewport: z.object({
      width: z.number(),
      height: z.number()
    }).optional(),
    baselineImagePath: z.string().optional(),
    baselineImage: z.string().optional()
  },
  async (params) =&amp;gt; {
    // Get auto login configuration
    const autoLoginConfig = {
      username: process.env.AUTO_LOGIN_USERNAME,
      password: process.env.AUTO_LOGIN_PASSWORD,
      usernameSelector: process.env.AUTO_LOGIN_USERNAME_SELECTOR || "#username",
      passwordSelector: process.env.AUTO_LOGIN_PASSWORD_SELECTOR || "#password",
      submitSelector: process.env.AUTO_LOGIN_SUBMIT_SELECTOR || 'button[type="submit"]',
      successSelector: process.env.AUTO_LOGIN_SUCCESS_SELECTOR,
      loginUrlPattern: process.env.AUTO_LOGIN_URL_PATTERN || "login|signin|auth",
    };

    // Get test configuration
    const testConfig = {
      selector: process.env.TEST_SELECTOR || params.selector,
      waitForSelector: process.env.TEST_WAIT_FOR_SELECTOR || params.waitForSelector,
      waitForTimeout: process.env.TEST_WAIT_TIMEOUT ? parseInt(process.env.TEST_WAIT_TIMEOUT) : params.waitForTimeout,
      threshold: process.env.TEST_THRESHOLD ? parseInt(process.env.TEST_THRESHOLD) : params.threshold,
      ignoreSelectors: process.env.TEST_IGNORE_SELECTORS ? process.env.TEST_IGNORE_SELECTORS.split(',') : params.ignoreSelectors,
      viewport: {
        width: process.env.TEST_VIEWPORT_WIDTH ? parseInt(process.env.TEST_VIEWPORT_WIDTH) : (params.viewport?.width || 1280),
        height: process.env.TEST_VIEWPORT_HEIGHT ? parseInt(process.env.TEST_VIEWPORT_HEIGHT) : (params.viewport?.height || 720)
      }
    };

    const result = await runVisualTest({
      url: params.url,
      ...testConfig,
      baselineImagePath: params.baselineImagePath,
      baselineImage: params.baselineImage,
      autoLogin: autoLoginConfig,
    });

    if (result.success) {
      if (result.baselineCreated || result.baselineUpdated) {
        return {
          content: [{
            type: "text",
            text: result.message || "Updated baseline image"
          }]
        };
      }

      return {
        content: [{
          type: "text",
          text: `Diff pixels: ${result.diffPixels}, Threshold: ${result.threshold}, Test ${result.passed ? 'passed' : 'failed'}`
        }, {
          type: "image",
          data: result.screenshots?.current || "",
          mimeType: "image/png"
        }, {
          type: "image",
          data: result.screenshots?.diff || "",
          mimeType: "image/png"
        }]
      };
    }

    return {
      content: [{
        type: "text",
        text: result.error || "Unknown error"
      }]
    };
  }
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Project Repository
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/@anguske/mcp-playwright-visual-test" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/@anguske/mcp-playwright-visual-test&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Configure Cursor
&lt;/h3&gt;

&lt;p&gt;To use this tool in Cursor, add the following configuration to &lt;code&gt;.cursor/mcp.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "mcpServers": {
    "@anguske/mcp-playwright-visual-test": {
      "command": "npx",
      "args": ["-y", "@anguske/mcp-playwright-visual-test"],
      "env": {
        "AUTO_LOGIN_USERNAME": "",
        "AUTO_LOGIN_PASSWORD": "",
        "AUTO_LOGIN_USERNAME_SELECTOR": "#userNameSignIn",
        "AUTO_LOGIN_PASSWORD_SELECTOR": "#passwordSignIn",
        "AUTO_LOGIN_SUBMIT_SELECTOR": "input[type=\"submit\"]",
        "AUTO_LOGIN_SUCCESS_SELECTOR": "",
        "TEST_VIEWPORT_WIDTH": 1440,
        "TEST_VIEWPORT_HEIGHT": 800,
        "TEST_THRESHOLD": 20,
        "TEST_WAIT_TIMEOUT": 10000,
        "AUTO_LOGIN_URL_PATTERN": "login|signin|auth",
        "PROJECT_ROOT": "C:/project/root"
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configuration file defines the MCP server configuration information:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Server Configuration&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;command&lt;/code&gt;: Use &lt;code&gt;npx&lt;/code&gt; command to run the tool&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;args&lt;/code&gt;: Use &lt;code&gt;-y&lt;/code&gt; parameter to automatically confirm installation and run the &lt;code&gt;@anguske/mcp-playwright-visual-test&lt;/code&gt; package&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Environment Variable Configuration&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto login configuration:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AUTO_LOGIN_USERNAME&lt;/code&gt;: Login username&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTO_LOGIN_PASSWORD&lt;/code&gt;: Login password&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTO_LOGIN_USERNAME_SELECTOR&lt;/code&gt;: Username input field selector&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTO_LOGIN_PASSWORD_SELECTOR&lt;/code&gt;: Password input field selector&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTO_LOGIN_SUBMIT_SELECTOR&lt;/code&gt;: Submit button selector&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AUTO_LOGIN_SUCCESS_SELECTOR&lt;/code&gt;: Login success indicator selector&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Test configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TEST_VIEWPORT_WIDTH&lt;/code&gt;: Viewport width (1440px)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TEST_VIEWPORT_HEIGHT&lt;/code&gt;: Viewport height (800px)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TEST_THRESHOLD&lt;/code&gt;: Difference threshold (20)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TEST_WAIT_TIMEOUT&lt;/code&gt;: Wait timeout (10000ms)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Other configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;AUTO_LOGIN_URL_PATTERN&lt;/code&gt;: Login page URL matching pattern&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PROJECT_ROOT&lt;/code&gt;: Project root directory path&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;When you use this tool in Cursor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cursor reads this configuration file&lt;/li&gt;
&lt;li&gt;Starts the MCP server according to the configuration&lt;/li&gt;
&lt;li&gt;Runs tests using the configured environment variables&lt;/li&gt;
&lt;li&gt;Displays test results and screenshots&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, when you enter in Cursor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/test playwright-ui-test url="https://example.com"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Recognize this as a tool call&lt;/li&gt;
&lt;li&gt;Start the server using configured environment variables&lt;/li&gt;
&lt;li&gt;Call the &lt;code&gt;playwright-ui-test&lt;/code&gt; tool method&lt;/li&gt;
&lt;li&gt;Display test results and screenshots&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  6. Add Resource
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;testResult&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ResourceTemplate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;test://{id}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;list&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Test Result ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;}]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Best Practices
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Parameter Validation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use Zod for strict parameter validation&lt;/li&gt;
&lt;li&gt;Provide default values for optional parameters&lt;/li&gt;
&lt;li&gt;Use environment variables for configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Error Handling&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use try-catch to catch possible errors&lt;/li&gt;
&lt;li&gt;Return structured error information&lt;/li&gt;
&lt;li&gt;Clean up resources in finally block&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resource Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use meaningful URI templates&lt;/li&gt;
&lt;li&gt;Implement appropriate resource access control&lt;/li&gt;
&lt;li&gt;Manage temporary files and directories&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tool Design&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep tool functionality single-purpose&lt;/li&gt;
&lt;li&gt;Provide clear parameter documentation&lt;/li&gt;
&lt;li&gt;Return structured results&lt;/li&gt;
&lt;li&gt;Support multiple configuration methods&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuration Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use environment variables for configuration&lt;/li&gt;
&lt;li&gt;Provide reasonable default values&lt;/li&gt;
&lt;li&gt;Support multiple configuration sources&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;MCP provides a powerful framework for building interactions between AI models and external tools. Through proper use of Tools and Resources, we can create feature-rich and maintainable AI applications. Remember to follow best practices to ensure code maintainability and extensibility. In actual development, pay attention to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Proper code organization&lt;/li&gt;
&lt;li&gt;Comprehensive error handling&lt;/li&gt;
&lt;li&gt;Flexible configuration options&lt;/li&gt;
&lt;li&gt;Code testability&lt;/li&gt;
&lt;li&gt;User experience and feedback &lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
