The MCP Menu: How AI Discovers and Uses Your Tools
You created your first readFile tool in the previous article. Congratulations! But imagine arriving at a restaurant without a menu. How would you know what’s available? This is exactly the problem that the MCP discovery system solves. Today, we’ll implement the complete “menu” that allows an AI to automatically discover all your tools and use them intelligently.
Introduction
In my 15 years of API development, I’ve seen many integration systems. But MCP has something elegant: auto-discovery. Rather than hard-coding each integration, the AI queries your server to discover what it can do. It’s as if your API could introduce itself.
This approach changes everything. Instead of having specific connectors for each AI, you create a standard that all MCP-compatible AIs can understand. Once you master this system, you can expose dozens of tools without ever modifying the AI’s code.
Reminder: The Complete Journey of a Request
Before coding, let’s visualize the complete journey of an interaction between an AI and your MCP server. This is crucial to understand where the discovery system fits.
Phase 1: Discovery (what we’ll code today)
You: “Claude, list the files in my projects folder”
Claude: “I don’t know the tools of this server yet. Let me discover them…”
Claude → Server: GET /mcp/tools
Server → Claude: Here are all my available tools with their descriptions
Claude: “Ah! There’s a listFiles tool. That’s exactly what I need.”
Phase 2: Validation (handled by Claude application)
Claude Application → You: “Do you authorize the use of the listFiles tool on the /projects folder?”
You: “Yes, authorized”
Phase 3: Execution
Claude → Server: POST /mcp/execute with {"tool": "listFiles", "params": {"path": "/projects"}}
Server → Claude: Execution result
Claude → You: “Here are the files in your projects folder: …”
Today, we focus on Phase 1: discovery.
The Standard MCP Discovery Format
The MCP protocol defines a standard format for describing your tools. Here’s the JSON structure the AI expects:
<span class="p">{</span><span class="w">
</span><span class="nl">"protocol_version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"server_info"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My MCP Server"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MCP server for local file access"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"tools"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"readFile"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Reads the content of a text file"</span><span class="p">,</span><span class="w">
</span><span class="nl">"input_schema"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"object"</span><span class="p">,</span><span class="w">
</span><span class="nl">"properties"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"file_path"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"string"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Path to the file to read"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"required"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"file_path"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>
This structure contains three key pieces of information:
protocol_version: Which version of MCP you’re implementing
server_info: Metadata about your server
tools: The complete list of your tools with their input schema
The input_schema uses the JSON Schema standard. It’s like auto-generated documentation that the AI can read and understand.
Creating the Protocol Manager
Let’s start by structuring our MCP protocol implementation. Create the file src/mcp/protocol.ts:
<span class="c1">// src/mcp/protocol.ts</span>
<span class="cm">/**
* Implemented MCP protocol version
*/</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">MCP_PROTOCOL_VERSION</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">1.0</span><span class="dl">"</span><span class="p">;</span>
<span class="cm">/**
* Server information
*/</span>
<span class="k">export</span> <span class="kr">interface</span> <span class="nx">ServerInfo</span> <span class="p">{</span>
<span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">version</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">description</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">author</span><span class="p">?:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">capabilities</span><span class="p">?:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="p">}</span>
<span class="cm">/**
* Input schema for a tool (JSON Schema)
*/</span>
<span class="k">export</span> <span class="kr">interface</span> <span class="nx">InputSchema</span> <span class="p">{</span>
<span class="nl">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">;</span>
<span class="nl">properties</span><span class="p">:</span> <span class="p">{</span>
<span class="p">[</span><span class="na">paramName</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">description</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">enum</span><span class="p">?:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="nl">default</span><span class="p">?:</span> <span class="kr">any</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">};</span>
<span class="nl">required</span><span class="p">:</span> <span class="kr">string</span><span class="p">[];</span>
<span class="p">}</span>
<span class="cm">/**
* Complete description of an MCP tool
*/</span>
<span class="k">export</span> <span class="kr">interface</span> <span class="nx">ToolDescription</span> <span class="p">{</span>
<span class="nl">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">description</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">input_schema</span><span class="p">:</span> <span class="nx">InputSchema</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/**
* Complete discovery response
*/</span>
<span class="k">export</span> <span class="kr">interface</span> <span class="nx">DiscoveryResponse</span> <span class="p">{</span>
<span class="nl">protocol_version</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">server_info</span><span class="p">:</span> <span class="nx">ServerInfo</span><span class="p">;</span>
<span class="nl">tools</span><span class="p">:</span> <span class="nx">ToolDescription</span><span class="p">[];</span>
<span class="p">}</span>
<span class="cm">/**
* Our server information
*/</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">SERVER_INFO</span><span class="p">:</span> <span class="nx">ServerInfo</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">MCP File Server</span><span class="dl">"</span><span class="p">,</span>
<span class="na">version</span><span class="p">:</span> <span class="dl">"</span><span class="s2">1.0.0</span><span class="dl">"</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">MCP server for local file management</span><span class="dl">"</span><span class="p">,</span>
<span class="na">author</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Nicolas Dabène</span><span class="dl">"</span><span class="p">,</span>
<span class="na">capabilities</span><span class="p">:</span> <span class="p">[</span>
<span class="dl">"</span><span class="s2">file_reading</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">directory_listing</span><span class="dl">"</span><span class="p">,</span>
<span class="dl">"</span><span class="s2">file_search</span><span class="dl">"</span>
<span class="p">]</span>
<span class="p">};</span>
These TypeScript types give us a solid structure. Each tool will have to provide a ToolDescription compliant with this format.
Converting Our Tools to MCP Format
Currently, our tools have a simple definition. Let’s transform them to the complete MCP format. Modify src/tools/readFile.ts:
<span class="c1">// src/tools/readFile.ts</span>
<span class="k">import</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">fs/promises</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">path</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ToolResponse</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../types/mcp</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ToolDescription</span><span class="p">,</span> <span class="nx">InputSchema</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../mcp/protocol</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kr">interface</span> <span class="nx">ReadFileParams</span> <span class="p">{</span>
<span class="nl">file_path</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">encoding</span><span class="p">?:</span> <span class="dl">'</span><span class="s1">utf-8</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">ascii</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">base64</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">readFile</span><span class="p">(</span><span class="nx">params</span><span class="p">:</span> <span class="nx">ReadFileParams</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">ToolResponse</span><span class="o">></span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">params</span><span class="p">.</span><span class="nx">file_path</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The 'file_path' parameter is required</span><span class="dl">"</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">absolutePath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">params</span><span class="p">.</span><span class="nx">file_path</span><span class="p">);</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">access</span><span class="p">(</span><span class="nx">absolutePath</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="s2">`File not found: </span><span class="p">${</span><span class="nx">params</span><span class="p">.</span><span class="nx">file_path</span><span class="p">}</span><span class="s2">`</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">stats</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">stat</span><span class="p">(</span><span class="nx">absolutePath</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">stats</span><span class="p">.</span><span class="nx">isFile</span><span class="p">())</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The specified path is not a file</span><span class="dl">"</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">MAX_FILE_SIZE</span> <span class="o">=</span> <span class="mi">10</span> <span class="o">*</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">stats</span><span class="p">.</span><span class="nx">size</span> <span class="o">></span> <span class="nx">MAX_FILE_SIZE</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="s2">`File too large (max </span><span class="p">${</span><span class="nx">MAX_FILE_SIZE</span> <span class="o">/</span> <span class="mi">1024</span> <span class="o">/</span> <span class="mi">1024</span><span class="p">}</span><span class="s2"> MB)`</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">encoding</span> <span class="o">=</span> <span class="nx">params</span><span class="p">.</span><span class="nx">encoding</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">utf-8</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">content</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readFile</span><span class="p">(</span><span class="nx">absolutePath</span><span class="p">,</span> <span class="nx">encoding</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">content</span><span class="p">:</span> <span class="nx">content</span><span class="p">,</span>
<span class="na">metadata</span><span class="p">:</span> <span class="p">{</span>
<span class="na">path</span><span class="p">:</span> <span class="nx">absolutePath</span><span class="p">,</span>
<span class="na">size</span><span class="p">:</span> <span class="nx">stats</span><span class="p">.</span><span class="nx">size</span><span class="p">,</span>
<span class="na">encoding</span><span class="p">:</span> <span class="nx">encoding</span><span class="p">,</span>
<span class="na">lastModified</span><span class="p">:</span> <span class="nx">stats</span><span class="p">.</span><span class="nx">mtime</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="na">error</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="s2">`Error reading file: </span><span class="p">${</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">}</span><span class="s2">`</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Input schema in JSON Schema format
*/</span>
<span class="kd">const</span> <span class="nx">readFileInputSchema</span><span class="p">:</span> <span class="nx">InputSchema</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">,</span>
<span class="na">properties</span><span class="p">:</span> <span class="p">{</span>
<span class="na">file_path</span><span class="p">:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Absolute or relative path to the file to read</span><span class="dl">"</span>
<span class="p">},</span>
<span class="na">encoding</span><span class="p">:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">File encoding</span><span class="dl">"</span><span class="p">,</span>
<span class="na">enum</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">ascii</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">base64</span><span class="dl">"</span><span class="p">],</span>
<span class="na">default</span><span class="p">:</span> <span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">required</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">file_path</span><span class="dl">"</span><span class="p">]</span>
<span class="p">};</span>
<span class="cm">/**
* Complete MCP description of the tool
*/</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">readFileDescription</span><span class="p">:</span> <span class="nx">ToolDescription</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">readFile</span><span class="dl">"</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Reads the content of a text file from the local file system. Supports different encodings (UTF-8, ASCII, Base64).</span><span class="dl">"</span><span class="p">,</span>
<span class="na">input_schema</span><span class="p">:</span> <span class="nx">readFileInputSchema</span>
<span class="p">};</span>
See the difference? We now have:
A formal JSON schema that describes exactly the expected parameters
Enriched metadata like default values and enumerations
A detailed description that helps the AI understand when to use this tool
Let’s do the same for listFiles. Modify src/tools/listFiles.ts:
<span class="c1">// src/tools/listFiles.ts</span>
<span class="k">import</span> <span class="nx">fs</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">fs/promises</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">path</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ToolResponse</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../types/mcp</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ToolDescription</span><span class="p">,</span> <span class="nx">InputSchema</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../mcp/protocol</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kr">interface</span> <span class="nx">ListFilesParams</span> <span class="p">{</span>
<span class="nl">directory_path</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="nl">include_hidden</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
<span class="nl">recursive</span><span class="p">?:</span> <span class="nx">boolean</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">listFiles</span><span class="p">(</span><span class="nx">params</span><span class="p">:</span> <span class="nx">ListFilesParams</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">ToolResponse</span><span class="o">></span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">params</span><span class="p">.</span><span class="nx">directory_path</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The 'directory_path' parameter is required</span><span class="dl">"</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">absolutePath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">resolve</span><span class="p">(</span><span class="nx">params</span><span class="p">.</span><span class="nx">directory_path</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">stats</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">stat</span><span class="p">(</span><span class="nx">absolutePath</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">stats</span><span class="p">.</span><span class="nx">isDirectory</span><span class="p">())</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The specified path is not a directory</span><span class="dl">"</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="c1">// Read directory content</span>
<span class="kd">let</span> <span class="nx">files</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">readdir</span><span class="p">(</span><span class="nx">absolutePath</span><span class="p">);</span>
<span class="c1">// Filter hidden files if necessary</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">params</span><span class="p">.</span><span class="nx">include_hidden</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">files</span> <span class="o">=</span> <span class="nx">files</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">file</span> <span class="o">=></span> <span class="o">!</span><span class="nx">file</span><span class="p">.</span><span class="nx">startsWith</span><span class="p">(</span><span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">));</span>
<span class="p">}</span>
<span class="c1">// Get details for each file</span>
<span class="kd">const</span> <span class="nx">filesWithDetails</span> <span class="o">=</span> <span class="k">await</span> <span class="nb">Promise</span><span class="p">.</span><span class="nx">all</span><span class="p">(</span>
<span class="nx">files</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="k">async</span> <span class="p">(</span><span class="nx">file</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">filePath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="nx">absolutePath</span><span class="p">,</span> <span class="nx">file</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">fileStats</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">fs</span><span class="p">.</span><span class="nx">stat</span><span class="p">(</span><span class="nx">filePath</span><span class="p">);</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">file</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="nx">fileStats</span><span class="p">.</span><span class="nx">isDirectory</span><span class="p">()</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">directory</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">file</span><span class="dl">'</span><span class="p">,</span>
<span class="na">size</span><span class="p">:</span> <span class="nx">fileStats</span><span class="p">.</span><span class="nx">size</span><span class="p">,</span>
<span class="na">lastModified</span><span class="p">:</span> <span class="nx">fileStats</span><span class="p">.</span><span class="nx">mtime</span><span class="p">,</span>
<span class="na">permissions</span><span class="p">:</span> <span class="nx">fileStats</span><span class="p">.</span><span class="nx">mode</span>
<span class="p">};</span>
<span class="p">})</span>
<span class="p">);</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">content</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">filesWithDetails</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
<span class="na">metadata</span><span class="p">:</span> <span class="p">{</span>
<span class="na">path</span><span class="p">:</span> <span class="nx">absolutePath</span><span class="p">,</span>
<span class="na">count</span><span class="p">:</span> <span class="nx">filesWithDetails</span><span class="p">.</span><span class="nx">length</span><span class="p">,</span>
<span class="na">include_hidden</span><span class="p">:</span> <span class="nx">params</span><span class="p">.</span><span class="nx">include_hidden</span> <span class="o">||</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="na">error</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="s2">`Error reading directory: </span><span class="p">${</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">}</span><span class="s2">`</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">listFilesInputSchema</span><span class="p">:</span> <span class="nx">InputSchema</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">object</span><span class="dl">"</span><span class="p">,</span>
<span class="na">properties</span><span class="p">:</span> <span class="p">{</span>
<span class="na">directory_path</span><span class="p">:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">string</span><span class="dl">"</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Absolute or relative path to the directory to list</span><span class="dl">"</span>
<span class="p">},</span>
<span class="na">include_hidden</span><span class="p">:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">boolean</span><span class="dl">"</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Include hidden files (starting with .)</span><span class="dl">"</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">},</span>
<span class="na">recursive</span><span class="p">:</span> <span class="p">{</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">boolean</span><span class="dl">"</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">List subdirectories recursively</span><span class="dl">"</span><span class="p">,</span>
<span class="na">default</span><span class="p">:</span> <span class="kc">false</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">required</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">directory_path</span><span class="dl">"</span><span class="p">]</span>
<span class="p">};</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">listFilesDescription</span><span class="p">:</span> <span class="nx">ToolDescription</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">listFiles</span><span class="dl">"</span><span class="p">,</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Lists files and folders in a given directory. Can include hidden files and support recursion.</span><span class="dl">"</span><span class="p">,</span>
<span class="na">input_schema</span><span class="p">:</span> <span class="nx">listFilesInputSchema</span>
<span class="p">};</span>
Creating the Centralized Tool Registry
Now, let’s create a registry that gathers all our tools. Create src/mcp/registry.ts:
<span class="c1">// src/mcp/registry.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ToolDescription</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./protocol</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ToolResponse</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../types/mcp</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">readFile</span><span class="p">,</span> <span class="nx">readFileDescription</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../tools/readFile</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">listFiles</span><span class="p">,</span> <span class="nx">listFilesDescription</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../tools/listFiles</span><span class="dl">'</span><span class="p">;</span>
<span class="cm">/**
* Type for a tool function
*/</span>
<span class="kd">type</span> <span class="nx">ToolFunction</span> <span class="o">=</span> <span class="p">(</span><span class="nx">params</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">ToolResponse</span><span class="o">></span><span class="p">;</span>
<span class="cm">/**
* Central registry of all available tools
*/</span>
<span class="kd">class</span> <span class="nx">ToolRegistry</span> <span class="p">{</span>
<span class="k">private</span> <span class="nx">tools</span><span class="p">:</span> <span class="nb">Map</span><span class="o"><</span><span class="kr">string</span><span class="p">,</span> <span class="nx">ToolFunction</span><span class="o">></span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">();</span>
<span class="k">private</span> <span class="nx">descriptions</span><span class="p">:</span> <span class="nb">Map</span><span class="o"><</span><span class="kr">string</span><span class="p">,</span> <span class="nx">ToolDescription</span><span class="o">></span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Map</span><span class="p">();</span>
<span class="cm">/**
* Register a new tool
*/</span>
<span class="nx">register</span><span class="p">(</span><span class="nx">description</span><span class="p">:</span> <span class="nx">ToolDescription</span><span class="p">,</span> <span class="nx">implementation</span><span class="p">:</span> <span class="nx">ToolFunction</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">tools</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">description</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">implementation</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">descriptions</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="nx">description</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">description</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`✅ Tool registered: </span><span class="p">${</span><span class="nx">description</span><span class="p">.</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Get all available tools
*/</span>
<span class="nx">getAllDescriptions</span><span class="p">():</span> <span class="nx">ToolDescription</span><span class="p">[]</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">descriptions</span><span class="p">.</span><span class="nx">values</span><span class="p">());</span>
<span class="p">}</span>
<span class="cm">/**
* Get the description of a specific tool
*/</span>
<span class="nx">getDescription</span><span class="p">(</span><span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">ToolDescription</span> <span class="o">|</span> <span class="kc">undefined</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">descriptions</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">toolName</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Execute a tool
*/</span>
<span class="k">async</span> <span class="nx">execute</span><span class="p">(</span><span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span> <span class="nx">params</span><span class="p">:</span> <span class="kr">any</span><span class="p">):</span> <span class="nb">Promise</span><span class="o"><</span><span class="nx">ToolResponse</span><span class="o">></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">tool</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">tools</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="nx">toolName</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">tool</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="s2">`Tool '</span><span class="p">${</span><span class="nx">toolName</span><span class="p">}</span><span class="s2">' not found. Available tools: </span><span class="p">${</span><span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">tools</span><span class="p">.</span><span class="nx">keys</span><span class="p">()).</span><span class="nx">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">, </span><span class="dl">'</span><span class="p">)}</span><span class="s2">`</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="k">try</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">await</span> <span class="nx">tool</span><span class="p">(</span><span class="nx">params</span><span class="p">);</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="na">error</span><span class="p">:</span> <span class="kr">any</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="s2">`Error executing '</span><span class="p">${</span><span class="nx">toolName</span><span class="p">}</span><span class="s2">': </span><span class="p">${</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">}</span><span class="s2">`</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/**
* Check if a tool exists
*/</span>
<span class="nx">has</span><span class="p">(</span><span class="nx">toolName</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">tools</span><span class="p">.</span><span class="nx">has</span><span class="p">(</span><span class="nx">toolName</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/**
* Get the number of registered tools
*/</span>
<span class="nx">count</span><span class="p">():</span> <span class="kr">number</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">tools</span><span class="p">.</span><span class="nx">size</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="c1">// Singleton instance of the registry</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">toolRegistry</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ToolRegistry</span><span class="p">();</span>
<span class="c1">// Register all our tools at startup</span>
<span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="nx">readFileDescription</span><span class="p">,</span> <span class="nx">readFile</span><span class="p">);</span>
<span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">register</span><span class="p">(</span><span class="nx">listFilesDescription</span><span class="p">,</span> <span class="nx">listFiles</span><span class="p">);</span>
This registry is the heart of our system. It’s the one that:
Maintains the list of all available tools
Manages tool execution in a unified way
Provides descriptions for discovery
Handles errors in a centralized manner
Implementing MCP Endpoints
Now let’s modify our Express server to implement standard MCP endpoints. Replace src/index.ts:
<span class="c1">// src/index.ts</span>
<span class="k">import</span> <span class="nx">express</span><span class="p">,</span> <span class="p">{</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">Response</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">express</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">MCP_PROTOCOL_VERSION</span><span class="p">,</span> <span class="nx">SERVER_INFO</span><span class="p">,</span> <span class="nx">DiscoveryResponse</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./mcp/protocol</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">toolRegistry</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./mcp/registry</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="nx">express</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">PORT</span> <span class="o">=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="c1">// Middleware</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">(</span><span class="nx">express</span><span class="p">.</span><span class="nx">json</span><span class="p">());</span>
<span class="c1">// Logging middleware</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">use</span><span class="p">((</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`[</span><span class="p">${</span><span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">()}</span><span class="s2">] </span><span class="p">${</span><span class="nx">req</span><span class="p">.</span><span class="nx">method</span><span class="p">}</span><span class="s2"> </span><span class="p">${</span><span class="nx">req</span><span class="p">.</span><span class="nx">path</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="nx">next</span><span class="p">();</span>
<span class="p">});</span>
<span class="c1">// ============================================</span>
<span class="c1">// STANDARD MCP ROUTES</span>
<span class="c1">// ============================================</span>
<span class="cm">/**
* Root endpoint - Server information
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span>
<span class="na">message</span><span class="p">:</span> <span class="dl">'</span><span class="s1">MCP File Server</span><span class="dl">'</span><span class="p">,</span>
<span class="na">version</span><span class="p">:</span> <span class="nx">SERVER_INFO</span><span class="p">.</span><span class="nx">version</span><span class="p">,</span>
<span class="na">protocol_version</span><span class="p">:</span> <span class="nx">MCP_PROTOCOL_VERSION</span><span class="p">,</span>
<span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">operational</span><span class="dl">'</span><span class="p">,</span>
<span class="na">endpoints</span><span class="p">:</span> <span class="p">{</span>
<span class="na">discovery</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/mcp/tools</span><span class="dl">'</span><span class="p">,</span>
<span class="na">execute</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/mcp/execute</span><span class="dl">'</span><span class="p">,</span>
<span class="na">health</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/health</span><span class="dl">'</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="cm">/**
* Discovery endpoint - The complete "menu"
* This is where the AI discovers all your tools
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp/tools</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="na">response</span><span class="p">:</span> <span class="nx">DiscoveryResponse</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">protocol_version</span><span class="p">:</span> <span class="nx">MCP_PROTOCOL_VERSION</span><span class="p">,</span>
<span class="na">server_info</span><span class="p">:</span> <span class="nx">SERVER_INFO</span><span class="p">,</span>
<span class="na">tools</span><span class="p">:</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">getAllDescriptions</span><span class="p">()</span>
<span class="p">};</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`📋 Discovery requested - </span><span class="p">${</span><span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">count</span><span class="p">()}</span><span class="s2"> available tools`</span><span class="p">);</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">response</span><span class="p">);</span>
<span class="p">});</span>
<span class="cm">/**
* Unified execution endpoint
* Format: POST /mcp/execute
* Body: { "tool": "toolName", "params": {...} }
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp/execute</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">tool</span><span class="p">,</span> <span class="nx">params</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
<span class="c1">// Validation</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">tool</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">400</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="dl">"</span><span class="s2">The 'tool' parameter is required</span><span class="dl">"</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`⚙️ Execution requested: </span><span class="p">${</span><span class="nx">tool</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="c1">// Execution via registry</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">execute</span><span class="p">(</span><span class="nx">tool</span><span class="p">,</span> <span class="nx">params</span> <span class="o">||</span> <span class="p">{});</span>
<span class="c1">// Log result</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">success</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`✅ Execution successful: </span><span class="p">${</span><span class="nx">tool</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`❌ Execution failed: </span><span class="p">${</span><span class="nx">tool</span><span class="p">}</span><span class="s2"> - </span><span class="p">${</span><span class="nx">result</span><span class="p">.</span><span class="nx">error</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="p">});</span>
<span class="cm">/**
* Endpoint for specific tool description
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/mcp/tools/:toolName</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">toolName</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">description</span> <span class="o">=</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">getDescription</span><span class="p">(</span><span class="nx">toolName</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">description</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">res</span><span class="p">.</span><span class="nx">status</span><span class="p">(</span><span class="mi">404</span><span class="p">).</span><span class="nx">json</span><span class="p">({</span>
<span class="na">success</span><span class="p">:</span> <span class="kc">false</span><span class="p">,</span>
<span class="na">error</span><span class="p">:</span> <span class="s2">`Tool '</span><span class="p">${</span><span class="nx">toolName</span><span class="p">}</span><span class="s2">' not found`</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">description</span><span class="p">);</span>
<span class="p">});</span>
<span class="cm">/**
* Health check
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">/health</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">({</span>
<span class="na">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">healthy</span><span class="dl">'</span><span class="p">,</span>
<span class="na">uptime</span><span class="p">:</span> <span class="nx">process</span><span class="p">.</span><span class="nx">uptime</span><span class="p">(),</span>
<span class="na">tools_count</span><span class="p">:</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">count</span><span class="p">(),</span>
<span class="na">timestamp</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">toISOString</span><span class="p">()</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="c1">// ============================================</span>
<span class="c1">// BACKWARD COMPATIBILITY (optional)</span>
<span class="c1">// ============================================</span>
<span class="cm">/**
* Old direct endpoint (for quick tests)
* @deprecated Use /mcp/execute instead
*/</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">post</span><span class="p">(</span><span class="dl">'</span><span class="s1">/tools/:toolName</span><span class="dl">'</span><span class="p">,</span> <span class="k">async</span> <span class="p">(</span><span class="nx">req</span><span class="p">:</span> <span class="nx">Request</span><span class="p">,</span> <span class="nx">res</span><span class="p">:</span> <span class="nx">Response</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">toolName</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">params</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">params</span> <span class="o">=</span> <span class="nx">req</span><span class="p">.</span><span class="nx">body</span><span class="p">;</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`⚠️ Using old endpoint /tools/</span><span class="p">${</span><span class="nx">toolName</span><span class="p">}</span><span class="s2"> (deprecated)`</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">execute</span><span class="p">(</span><span class="nx">toolName</span><span class="p">,</span> <span class="nx">params</span><span class="p">);</span>
<span class="nx">res</span><span class="p">.</span><span class="nx">json</span><span class="p">(</span><span class="nx">result</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// ============================================</span>
<span class="c1">// SERVER STARTUP</span>
<span class="c1">// ============================================</span>
<span class="nx">app</span><span class="p">.</span><span class="nx">listen</span><span class="p">(</span><span class="nx">PORT</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">═══════════════════════════════════════</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">🚀 MCP File Server</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">═══════════════════════════════════════</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`📍 URL: http://localhost:</span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`📋 Discovery: http://localhost:</span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">/mcp/tools`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`⚙️ Execution: POST http://localhost:</span><span class="p">${</span><span class="nx">PORT</span><span class="p">}</span><span class="s2">/mcp/execute`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s2">`🔧 Available tools: </span><span class="p">${</span><span class="nx">toolRegistry</span><span class="p">.</span><span class="nx">count</span><span class="p">()}</span><span class="s2">`</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">═══════════════════════════════════════</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
Our server now implements the complete MCP protocol with three main endpoints:
GET /mcp/tools: Discovery of all tools
POST /mcp/execute: Unified execution of any tool
GET /mcp/tools/:toolName: Details of a specific tool
Testing the Discovery System
Let’s restart our server and test the complete system:
npm run dev
You should see:
═══════════════════════════════════════
🚀 MCP File Server
═══════════════════════════════════════
📍 URL: http://localhost:3000
📋 Discovery: http://localhost:3000/mcp/tools
⚙️ Execution: POST http://localhost:3000/mcp/execute
🔧 Available tools: 2
═══════════════════════════════════════
✅ Tool registered: readFile
✅ Tool registered: listFiles
Test 1: Complete Discovery
curl http://localhost:3000/mcp/tools | json_pp
Perfect! The AI can now discover all your tools with their complete descriptions.
Test 2: Execution via Unified Endpoint
curl <span class="nt">-X</span> POST http://localhost:3000/mcp/execute <span class="se">\</span>
<span class="nt">-H</span> <span class="s2">"Content-Type: application/json"</span> <span class="se">\</span>
<span class="nt">-d</span> <span class="s1">'{
"tool": "readFile",
"params": {
"file_path": "test.txt"
}
}'</span>
Understanding the Architectural Impact
This discovery system fundamentally changes the architecture of AI integrations:
Before MCP: Rigid Integrations
Each AI had to be specifically programmed for each tool:
<span class="c1">// Code in the AI to integrate a specific tool</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">userWantsToReadFile</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">callReadFileAPI</span><span class="p">(</span><span class="nx">userParams</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">userWantsToListFiles</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">callListFilesAPI</span><span class="p">(</span><span class="nx">userParams</span><span class="p">);</span>
<span class="p">}</span>
After MCP: Auto-Discovery
The AI dynamically discovers capabilities:
<span class="c1">// The AI can now discover and use any tool</span>
<span class="kd">const</span> <span class="nx">tools</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">discoverTools</span><span class="p">();</span>
<span class="kd">const</span> <span class="nx">tool</span> <span class="o">=</span> <span class="nx">tools</span><span class="p">.</span><span class="nx">find</span><span class="p">(</span><span class="nx">t</span> <span class="o">=></span> <span class="nx">matchesUserRequest</span><span class="p">(</span><span class="nx">t</span><span class="p">));</span>
<span class="k">await</span> <span class="nx">executeTool</span><span class="p">(</span><span class="nx">tool</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="nx">userParams</span><span class="p">);</span>
Conclusion: A New Paradigm
The MCP discovery system represents a paradigm shift in AI integration. Instead of creating specific connectors for each use case, you create standardized “building blocks” that all compatible AIs can dynamically assemble.
In this article, we saw how to:
- Implement the complete MCP protocol with discovery and execution
- Create a centralized tool registry
- Structure tools with JSON Schema
- Test the system with HTTP clients
- Simulate complete AI-server interaction
The next step? Connect your MCP server to a real AI like Claude Desktop. You’ll then see the magic happen: your AI will be able to actually read your files, list your folders, and use all your custom tools.
MCP is not just a technical protocol: it’s a new way of thinking about human-AI integration. Your tools become natural extensions of AI capabilities, without technical friction.
Article published on November 12, 2025 by Nicolas Dabène - PHP & PrestaShop Expert with 15+ years of experience in software architecture and AI integration
Also read:
- Understanding the Model Context Protocol (MCP): A Simple Conversation
- Create Your First MCP Server: TypeScript Project Setup
- Create Your First MCP Tool: The readFile Tool Explained
- Secure Your MCP Server: Permissions, Validation and Protection
- Connect Your MCP Server to Claude Desktop: The Complete Integration