Build a Code Snippet Store MCP Server
Build an MCP server that lets AI assistants save and search code snippets.
Introduction
30 Second Summary
Every developer has snippets of code they reuse constantly. Imagine if your AI assistant could save, organize, and instantly retrieve those snippets for you whenever you needed them.
In this project, you will build a Model Context Protocol (MCP) server in TypeScript that stores and searches code snippets. AI assistants like Claude can connect to your server and manage your snippet collection on your behalf.
What You'll Build
A fully functional MCP server that acts as a personal code snippet library, exposing tools that any MCP-compatible AI client can call to save, retrieve, and search snippets.
By the end of this project, you'll have:
- A working MCP server with 4 registered tools for saving, listing, retrieving, and searching code snippets.
- A JSON file-based persistence layer so your snippets survive between server restarts.
- Hands-on experience testing your tools interactively with the official MCP Inspector.
- Secret Mission: Add a tagging system and expose your snippet collection as MCP Resources.
Are there any prerequisites?
You should be comfortable reading and writing TypeScript. No prior MCP experience is needed. Everything runs in your browser using StackBlitz, so there is nothing to install locally.
Set Up Your Project in StackBlitz
Every MCP server needs a Node.js runtime, a TypeScript compiler, and the official MCP SDK installed before you can start building tools.
Instead of installing these locally, you will use StackBlitz to get a full development environment running inside your browser. StackBlitz uses WebContainers to run Node.js directly in the browser tab, giving you a VS Code-style editor and a terminal with zero setup on your machine.
In this step, get ready to:
- Create a new Node.js project in StackBlitz.
- Configure TypeScript and install the MCP SDK.
- Verify the project compiles successfully.
Create a new Node.js project in StackBlitz
StackBlitz gives you a browser-based development environment with a built-in Node.js runtime. You need a Chromium-based browser (Chrome, Edge, or Brave) for the best compatibility.
- Open stackblitz.com in your browser.
- On the homepage, scroll down to find the project starters section.
- Click Node.js under the Blank project starters.
StackBlitz opens a new project with a code editor on the left, a file explorer sidebar, and a terminal panel at the bottom. You should see two default files: index.js and package.json.
What is StackBlitz?
StackBlitz runs a full Node.js environment inside your browser using a technology called WebContainers. You get a terminal, npm, and a file system without installing anything on your computer.
Think of it as VS Code plus Node.js, entirely in a browser tab.
Not seeing the Node.js starter?
Make sure you are using Chrome, Edge, or Brave. Firefox and Safari have limited WebContainers support. If you still cannot find the starter, navigate directly to stackblitz.com/edit/node in your address bar.
help me open a Node.js project in StackBlitz
Initialize the project structure
The default StackBlitz Node.js project is minimal. You need to replace its contents with the configuration your MCP server requires: a package.json that declares your dependencies, and a tsconfig.json that tells the TypeScript compiler how to build your code.
- Click on package.json in the file explorer to open it in the editor.
- Select all the existing content and replace it with the following:
{
"name": "snippet-store-mcp",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^3.25.0"
},
"devDependencies": {
"@types/node": "^22.12.0",
"typescript": "^5.5.4"
}
}
What does this configuration do?
`"type": "module"` tells Node.js to use ES module syntax (import/export) instead of the older CommonJS require() style.
The build script runs the TypeScript compiler, and start runs the compiled JavaScript. The @modelcontextprotocol/sdk package is the official MCP SDK, and zod is used to define input schemas for your tools.
You will not see a visible change yet. The next file and the npm install command will bring everything together.
✔️ Awesome, I've got everything!
Great. Make sure you have replaced all the original content in package.json.
ⓧ I'd like to double check the full code
{
"name": "snippet-store-mcp",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^3.25.0"
},
"devDependencies": {
"@types/node": "^22.12.0",
"typescript": "^5.5.4"
}
}
- Create a new file called tsconfig.json by clicking the New File icon (a page with a plus symbol) at the top of the file explorer sidebar.
- Type tsconfig.json as the filename and press Enter.
- Paste the following configuration into the new file:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
What does this TypeScript config do?
`"module": "NodeNext"` tells TypeScript to emit modern ES modules that work with Node.js. The outDir setting puts compiled JavaScript into a dist/ folder, keeping your source code in src/ separate from the build output.
`"strict": true` enables the strongest type checking, helping you catch errors before your MCP server runs.
You will not see a change yet. The TypeScript compiler uses this file when you run npx tsc in the next substep.
✔️ Awesome, I've got everything!
Great. Confirm tsconfig.json appears in your file explorer at the project root (same level as package.json).
ⓧ I'd like to double check the full code
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
- Install the project dependencies by running this command in the StackBlitz terminal:
npm install
- Wait for the installation to finish. You should see output listing the installed packages, and a node_modules folder appears in your file explorer.
Seeing installation errors?
If npm reports errors about missing packages or version conflicts, double-check your package.json matches the code above exactly. A misplaced comma or missing quote in JSON causes npm to fail.
If StackBlitz auto-installed packages before you finished editing package.json, run npm install again after saving your final version.
help me fix npm install errors in StackBlitz
Verify the setup compiles
Now that your dependencies are installed and TypeScript is configured, you need a source file to compile. You will create a minimal src/index.ts that imports the MCP SDK to prove the entire toolchain works.
- Create a new folder called src by clicking the New Folder icon in the file explorer sidebar.
- Type src as the folder name and press Enter.
- Inside the src folder, create a new file called index.ts and paste the following code:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
console.log("MCP SDK imported successfully");
What does this code do?
This imports the McpServer class from the official MCP SDK. The console.log is a quick sanity check so you can confirm the import resolves correctly when you compile.
In the next step, you will replace this placeholder with the actual server setup code.
✔️ Awesome, I've got everything!
Great. Confirm src/index.ts appears inside the src folder in your file explorer.
ⓧ I'd like to double check the full code
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
console.log("MCP SDK imported successfully");
- Compile the project by running this command in the terminal:
npx tsc
- The command should complete with no output (no errors means success).
- Check your file explorer. A new dist/ folder should appear containing index.js (the compiled JavaScript version of your TypeScript file).
Seeing TypeScript errors?
If you see errors mentioning Cannot find module, make sure npm install completed successfully. The SDK must be installed in node_modules before TypeScript can resolve the import.
If you see rootDir errors, confirm your index.ts file is inside the src/ folder, not at the project root. The tsconfig.json expects all source files to live in src/.
help me fix TypeScript compilation errors in my MCP server project
Delete the default index.js file
The StackBlitz Node.js starter includes a default index.js file at the project root. You can right-click it in the file explorer and select Delete to keep your project tidy. It is not needed.
Your development environment is ready. Next up, you will build the actual MCP server skeleton that clients can connect to.
Create the MCP Server Skeleton
Your project compiles and the toolchain is ready. Now it's time to write the actual MCP server code.
An MCP server needs two things to come alive: a server instance that knows its own name and version, and a transport layer that connects it to clients. You'll also add a storage layer so your server can persist snippets to disk between sessions.
In this step, get ready to:
- Create an McpServer instance with metadata and a stdio transport connection.
- Build a snippet storage layer with JSON file persistence.
- Compile and run the server to confirm it starts correctly.
Set up the McpServer instance and transport
Every MCP server starts with two pieces: an McpServer object that holds server metadata, and a transport that handles communication with clients. The StdioServerTransport uses standard input/output streams for this communication.
- Replace the entire contents of src/index.ts with the following server skeleton:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
// Create the MCP server with a name and version
const server = new McpServer({
name: "snippet-store",
version: "1.0.0",
});
// Start the server and connect via stdio transport
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Snippet Store MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
What does this code do?
The four imports pull in everything you need: the MCP server class, the stdio transport, Zod for schema validation (used later for tool inputs), and Node.js file system functions for persistence.
The McpServer constructor takes a name and version that identify your server to any MCP client that connects. The main() function wires up the StdioServerTransport and starts listening.
The startup message goes to console.error (stderr) because MCP servers using stdio transport reserve stdout for JSON-RPC protocol messages.
- Save src/index.ts.
Seeing a red underline on an import?
Make sure the import paths end with .js (not .ts). TypeScript with NodeNext module resolution requires the .js extension in import paths, even though the source files are .ts.
If StackBlitz still shows errors, try clicking the terminal and running npx tsc to see specific error messages.
Help me debug these import errors.
Add the snippet storage layer
Your server needs a way to store, load, and persist code snippets. You'll define the shape of a snippet, keep them in an in-memory Map, and write two helper functions that sync the Map to a JSON file on disk.
- In src/index.ts, find this line:
// Create the MCP server with a name and version
const server = new McpServer({
- Add the following storage layer code directly above that comment. Place it after the imports and before the const server line:
// Define the shape of a stored snippet
interface Snippet {
id: string;
title: string;
language: string;
code: string;
createdAt: string;
}
// Path to the JSON file and in-memory storage
const SNIPPETS_FILE = "snippets.json";
const snippets: Map<string, Snippet> = new Map();
// Load existing snippets from disk on startup
function loadSnippets(): void {
if (existsSync(SNIPPETS_FILE)) {
const data = readFileSync(SNIPPETS_FILE, "utf-8");
const parsed: Snippet[] = JSON.parse(data);
for (const snippet of parsed) {
snippets.set(snippet.id, snippet);
}
}
}
// Write the current snippet Map to disk
function saveSnippets(): void {
const data = Array.from(snippets.values());
writeFileSync(SNIPPETS_FILE, JSON.stringify(data, null, 2));
}
// Generate a unique ID using timestamp and random characters
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
}
What does this code do?
The Snippet interface defines the data shape: every snippet has an ID, title, language, code content, and a creation timestamp. The snippets Map holds these in memory, keyed by ID for fast lookups.
The loadSnippets() function checks if snippets.json exists on disk and, if so, reads it into the Map. The saveSnippets() function writes the Map back to disk whenever the data changes.
The generateId() function creates short, unique IDs by combining a base-36 timestamp with random characters.
Now that loadSnippets() exists, you need to call it when the server starts up.
- In the main() function, find this line:
const transport = new StdioServerTransport();
- Add loadSnippets(); on a new line directly above it, so main() now looks like this:
async function main() {
loadSnippets();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Snippet Store MCP Server running on stdio");
}
- Save src/index.ts.
Seeing a type error?
Make sure the interface Snippet block and the three helper functions are placed above the const server line, not below it. Also check for missing closing braces on the loadSnippets function.
Help me fix this type error.
Build and run the server
Time to compile your TypeScript and run the server to confirm everything works together.
- Compile the project by running this command in the StackBlitz terminal:
npx tsc
- If the compiler finishes with no output, your code is error-free. Run the server by entering:
node dist/index.js
- You should see Snippet Store MCP Server running on stdio printed in the terminal.
- Press Ctrl+C to stop the server. It hangs after printing the message because it's waiting for MCP client connections on stdin.
Why does the server hang?
MCP servers using stdio transport read JSON-RPC messages from stdin and write responses to stdout. When you run the server directly in a terminal, there's no client sending messages, so it waits indefinitely.
In the next step, you'll use the MCP Inspector which connects as a proper client.
Seeing a compile error?
Run npx tsc and check the error message. The most common issues are a missing closing brace in the interface or a typo in one of the function names.
If you see an error mentioning a missing module, double-check that your import paths match exactly, including the .js extensions.
Help me debug this compile error.
✔️ Awesome, I've got everything!
Great. Make sure you've saved src/index.ts and confirmed the startup message prints.
ⓧ I'd like to double check the full code
Here is the complete src/index.ts at this point:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
interface Snippet {
id: string;
title: string;
language: string;
code: string;
createdAt: string;
}
const SNIPPETS_FILE = "snippets.json";
const snippets: Map<string, Snippet> = new Map();
function loadSnippets(): void {
if (existsSync(SNIPPETS_FILE)) {
const data = readFileSync(SNIPPETS_FILE, "utf-8");
const parsed: Snippet[] = JSON.parse(data);
for (const snippet of parsed) {
snippets.set(snippet.id, snippet);
}
}
}
function saveSnippets(): void {
const data = Array.from(snippets.values());
writeFileSync(SNIPPETS_FILE, JSON.stringify(data, null, 2));
}
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
}
const server = new McpServer({
name: "snippet-store",
version: "1.0.0",
});
async function main() {
loadSnippets();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Snippet Store MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
Your MCP server skeleton is alive. It starts up, loads any existing snippets from disk, and waits for client connections. Next up, you'll register the tools that let clients actually save, list, and search snippets.
Register the Snippet Tools
Your server skeleton is running and can accept connections, but it has nothing to offer a client yet.
In the MCP protocol, tools are the actions an AI client can invoke on your server. Each tool declares a name, a description, and a validated input schema so the client knows exactly what it can ask for. In this step, you will register four tools that let any connected AI assistant save, list, retrieve, and search code snippets.
In this step, get ready to:
- Register the save-snippet tool with a Zod input schema.
- Register the list-snippets and get-snippet tools for browsing saved snippets.
- Register the search-snippets tool for keyword searching across your collection.
Register the save-snippet tool
The first tool your server needs is the ability to save a new snippet. When an AI client calls this tool, it passes a title, programming language, and code body. Your server generates a unique ID, stores the snippet, persists it to disk, and confirms success.
- In src/index.ts, find the line that says const server = new McpServer({ and scroll past its closing });.
- Directly below the server declaration (before the async function main() block), add the save-snippet tool by pasting this code:
server.registerTool(
"save-snippet",
{
description: "Save a new code snippet to the store",
inputSchema: {
title: z.string().describe("A short title for the snippet"),
language: z.string().describe("The programming language (e.g. typescript, python, rust)"),
code: z.string().describe("The code content of the snippet"),
},
},
async ({ title, language, code }) => {
const id = generateId();
const snippet: Snippet = {
id,
title,
language,
code,
createdAt: new Date().toISOString(),
};
snippets.set(id, snippet);
saveSnippets();
return {
content: [
{
type: "text" as const,
text: `Snippet saved successfully!\nID: ${id}\nTitle: ${title}\nLanguage: ${language}`,
},
],
};
}
);
What does this code do?
This registers a tool named save-snippet on your MCP server. The inputSchema uses Zod types to declare what the AI client must provide. The SDK auto-wraps this raw shape into a proper schema object.
The callback generates a unique ID, builds a Snippet object, stores it in the snippets Map, and calls saveSnippets() to write everything to disk. It then returns a text confirmation the client can show to the user.
- Save src/index.ts.
- Compile the project by running this command in the StackBlitz terminal:
npx tsc
- You should see no errors in the terminal output. A new dist/index.js file is generated with the compiled tool registration.
Seeing a TypeScript error?
Check that your server.registerTool( call is placed after the const server = new McpServer(...) block and before the async function main() function. The server variable must be declared before you call methods on it.
Also make sure you have the closing ); after the callback function. Missing parentheses or brackets are the most common cause of compile errors here.
help me fix this TypeScript compilation error in my MCP server
Register the list-snippets and get-snippet tools
Your server can save snippets, but an AI client also needs to browse what's already stored. These two tools handle discovery: one lists everything at a glance, and the other retrieves the full content of a specific snippet by ID.
- In src/index.ts, directly below the save-snippet tool's closing );, add the list-snippets tool:
server.registerTool(
"list-snippets",
{
description: "List all saved code snippets with their IDs, titles, and languages",
inputSchema: {},
},
async () => {
if (snippets.size === 0) {
return {
content: [{ type: "text" as const, text: "No snippets saved yet." }],
};
}
const list = Array.from(snippets.values())
.map((s) => `- [${s.id}] ${s.title} (${s.language})`)
.join("\n");
return {
content: [
{
type: "text" as const,
text: `Saved snippets (${snippets.size} total):\n${list}`,
},
],
};
}
);
What does this code do?
The list-snippets tool uses an empty inputSchema: {} because it requires no parameters. It returns either a "No snippets saved yet" message or a formatted list showing each snippet's ID, title, and language.
- Below the list-snippets tool, add the get-snippet tool:
server.registerTool(
"get-snippet",
{
description: "Retrieve a specific code snippet by its ID",
inputSchema: {
id: z.string().describe("The unique ID of the snippet to retrieve"),
},
},
async ({ id }) => {
const snippet = snippets.get(id);
if (!snippet) {
return {
content: [
{ type: "text" as const, text: `Snippet with ID "${id}" not found.` },
],
};
}
return {
content: [
{
type: "text" as const,
text: `Title: ${snippet.title}\nLanguage: ${snippet.language}\nCreated: ${snippet.createdAt}\n\n\`\`\`${snippet.language}\n${snippet.code}\n\`\`\``,
},
],
};
}
);
What does this code do?
The get-snippet tool takes an id string and looks it up in the Map. If the snippet exists, it returns the full details including the code wrapped in a markdown code fence. If not, it returns a friendly error message.
This pattern of returning structured content arrays with type: "text" is how MCP tools communicate results back to clients. The client decides how to display this text to the user.
- Save src/index.ts. You won't see a change yet. The next tool completes the set, and then you will compile to verify everything together.
Register the search-snippets tool
The final tool lets an AI client search across all snippet content. Instead of requiring the user to remember exact IDs, the client can search by keyword to find relevant code.
- Below the get-snippet tool in src/index.ts, add the search-snippets tool:
server.registerTool(
"search-snippets",
{
description: "Search snippets by matching a query against titles, languages, and code content",
inputSchema: {
query: z.string().describe("The search query to match against snippet content"),
},
},
async ({ query }) => {
const lowerQuery = query.toLowerCase();
const results = Array.from(snippets.values()).filter(
(s) =>
s.title.toLowerCase().includes(lowerQuery) ||
s.language.toLowerCase().includes(lowerQuery) ||
s.code.toLowerCase().includes(lowerQuery)
);
if (results.length === 0) {
return {
content: [
{
type: "text" as const,
text: `No snippets found matching "${query}".`,
},
],
};
}
const list = results
.map((s) => `- [${s.id}] ${s.title} (${s.language})`)
.join("\n");
return {
content: [
{
type: "text" as const,
text: `Found ${results.length} snippet(s) matching "${query}":\n${list}`,
},
],
};
}
);
What does this code do?
The search-snippets tool performs a case-insensitive search across three fields: title, language, and code. It converts both the query and each field to lowercase before comparing, so searching for "react" matches "React" or "REACT".
If matches are found, it returns a formatted list. If not, it returns a clear "no results" message the client can relay to the user.
- Save src/index.ts.
- Compile the full project to verify all four tools are registered correctly:
npx tsc
- You should see no errors. The TypeScript compiler validates that every tool's input schema and callback types are correct.
Compilation fails with multiple errors?
Make sure each server.registerTool( call ends with ); on its own line. A missing semicolon or closing parenthesis on one tool causes cascading errors in the tools below it.
Verify that your four tool registrations sit between the const server = new McpServer({...}); block and the async function main() block. Use the full code tab below to compare your file against the expected state.
help me debug these TypeScript errors in my MCP tool registrations
✔️ Awesome, I've got everything!
Great. Double check you have saved src/index.ts and that npx tsc completes with no errors.
ⓧ I'd like to double check the full code
Here is the complete src/index.ts file with all four tools registered:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
interface Snippet {
id: string;
title: string;
language: string;
code: string;
createdAt: string;
}
const SNIPPETS_FILE = "snippets.json";
const snippets: Map<string, Snippet> = new Map();
function loadSnippets(): void {
if (existsSync(SNIPPETS_FILE)) {
const data = readFileSync(SNIPPETS_FILE, "utf-8");
const parsed: Snippet[] = JSON.parse(data);
for (const snippet of parsed) {
snippets.set(snippet.id, snippet);
}
}
}
function saveSnippets(): void {
const data = Array.from(snippets.values());
writeFileSync(SNIPPETS_FILE, JSON.stringify(data, null, 2));
}
function generateId(): string {
return Date.now().toString(36) + Math.random().toString(36).substring(2, 8);
}
const server = new McpServer({
name: "snippet-store",
version: "1.0.0",
});
server.registerTool(
"save-snippet",
{
description: "Save a new code snippet to the store",
inputSchema: {
title: z.string().describe("A short title for the snippet"),
language: z.string().describe("The programming language (e.g. typescript, python, rust)"),
code: z.string().describe("The code content of the snippet"),
},
},
async ({ title, language, code }) => {
const id = generateId();
const snippet: Snippet = {
id,
title,
language,
code,
createdAt: new Date().toISOString(),
};
snippets.set(id, snippet);
saveSnippets();
return {
content: [
{
type: "text" as const,
text: `Snippet saved successfully!\nID: ${id}\nTitle: ${title}\nLanguage: ${language}`,
},
],
};
}
);
server.registerTool(
"list-snippets",
{
description: "List all saved code snippets with their IDs, titles, and languages",
inputSchema: {},
},
async () => {
if (snippets.size === 0) {
return {
content: [{ type: "text" as const, text: "No snippets saved yet." }],
};
}
const list = Array.from(snippets.values())
.map((s) => `- [${s.id}] ${s.title} (${s.language})`)
.join("\n");
return {
content: [
{
type: "text" as const,
text: `Saved snippets (${snippets.size} total):\n${list}`,
},
],
};
}
);
server.registerTool(
"get-snippet",
{
description: "Retrieve a specific code snippet by its ID",
inputSchema: {
id: z.string().describe("The unique ID of the snippet to retrieve"),
},
},
async ({ id }) => {
const snippet = snippets.get(id);
if (!snippet) {
return {
content: [
{ type: "text" as const, text: `Snippet with ID "${id}" not found.` },
],
};
}
return {
content: [
{
type: "text" as const,
text: `Title: ${snippet.title}\nLanguage: ${snippet.language}\nCreated: ${snippet.createdAt}\n\n\`\`\`${snippet.language}\n${snippet.code}\n\`\`\``,
},
],
};
}
);
server.registerTool(
"search-snippets",
{
description: "Search snippets by matching a query against titles, languages, and code content",
inputSchema: {
query: z.string().describe("The search query to match against snippet content"),
},
},
async ({ query }) => {
const lowerQuery = query.toLowerCase();
const results = Array.from(snippets.values()).filter(
(s) =>
s.title.toLowerCase().includes(lowerQuery) ||
s.language.toLowerCase().includes(lowerQuery) ||
s.code.toLowerCase().includes(lowerQuery)
);
if (results.length === 0) {
return {
content: [
{
type: "text" as const,
text: `No snippets found matching "${query}".`,
},
],
};
}
const list = results
.map((s) => `- [${s.id}] ${s.title} (${s.language})`)
.join("\n");
return {
content: [
{
type: "text" as const,
text: `Found ${results.length} snippet(s) matching "${query}":\n${list}`,
},
],
};
}
);
async function main() {
loadSnippets();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Snippet Store MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
Your snippet store now has a complete tool set. Next up, you will launch the MCP Inspector and test each tool interactively to see your server in action.
Test with the MCP Inspector
Your MCP server has four tools registered and ready to go. Now it's time to see them in action by connecting a real client.
The MCP Inspector is the official interactive testing tool for MCP servers. It spawns your server as a child process, connects over stdio, and gives you a web UI to call each tool manually. Think of it as a playground for verifying your server works before a real AI client like Claude connects to it.
In this step, get ready to:
- Build the project and launch the MCP Inspector.
- Test the save-snippet tool by saving 2-3 code snippets.
- Test list-snippets, get-snippet, and search-snippets to verify retrieval and search.
Build the project and launch the MCP Inspector
Before you can test, you need to compile your TypeScript into JavaScript that Node.js can run. Then you'll launch the Inspector, which starts both a proxy server and a web UI for interacting with your tools.
- Build your project by running this command in the StackBlitz terminal:
npm run build
- You should see no errors. The compiled JavaScript appears in the dist/ folder.
- Launch the MCP Inspector with your server by running:
npx @modelcontextprotocol/inspector node dist/index.js
What does this command do?
This single command does two things. It starts the MCP Inspector proxy on port 6277 and a web UI on port 6274. It also spawns your compiled server (node dist/index.js) as a child process and connects to it over stdio.
Your server communicates with the Inspector through stdin/stdout using the MCP protocol. That's why your server logs to stderr instead of stdout.
- Open the Inspector UI. In StackBlitz, the preview panel may automatically show port 6274. If not, open http://localhost:6274 in a new browser tab.
You should see the MCP Inspector web interface with your server connected.
Inspector not loading?
- Make sure you ran npm run build first. The Inspector needs the compiled dist/index.js file to exist.
- If StackBlitz shows a blank preview, try clicking the external-link icon to open port 6274 in a separate tab.
- Confirm you're using a Chromium-based browser (Chrome, Edge, or Brave). WebContainers have limited support in Firefox.
Help me troubleshoot the MCP Inspector not loading in StackBlitz.
Test the save-snippet tool
The Inspector's Tools tab lets you select any registered tool, fill in its parameters, and execute it. You'll use this to save a few snippets and confirm your server handles them correctly.
- In the Inspector UI, click the Tools tab in the navigation.
- You should see all four of your registered tools listed: save-snippet, list-snippets, get-snippet, and search-snippets.
- Select save-snippet from the list.
- The Inspector shows input fields for title, language, and code based on your Zod schema.
- Fill in the first snippet's parameters:
- title: Hello World.
- language: typescript.
- code: console.log("Hello, world!").
- Click Run Tool to execute.
You should see a response containing "Snippet saved successfully!" along with a generated ID, the title, and the language.
- Save a second snippet with different values. For example:
- title: Fetch Data.
- language: python.
- code: requests.get("https://api.example.com").
- Click Run Tool again to save it.
- Save a third snippet of your choice in any language you like.
Why save multiple snippets?
Having 2-3 snippets in different languages gives you meaningful data to test the retrieval and search tools next. It also proves that your persistence layer (the JSON file) accumulates snippets correctly across multiple calls.
Seeing an error when you run the tool?
- Check that you filled in all three fields (title, language, code). Empty strings are allowed but missing fields will cause a validation error.
- If you see a connection error, your server process may have crashed. Check the StackBlitz terminal for error messages and re-run the Inspector command.
Help me debug my save-snippet tool not working in the Inspector.
Test retrieval and search
Now that you have snippets saved, you can verify the other three tools work correctly.
- Select list-snippets from the tools list.
- Click Run Tool (this tool takes no parameters).
You should see a formatted list showing all your saved snippets with their IDs, titles, and languages.
- Copy one of the snippet IDs from the list-snippets result.
- Select get-snippet from the tools list.
- Paste the ID into the id field and click Run Tool.
You should see the full snippet details including the title, language, creation timestamp, and the complete code content.
- Select search-snippets from the tools list.
- Enter python in the query field and click Run Tool.
You should see your Python snippet in the results. The search matches against titles, languages, and code content.
- Try another search with a term like Hello to confirm the title-matching works.
- In the StackBlitz file explorer, check that a snippets.json file now exists in your project root. Open it to see your saved snippets persisted as JSON.
What is snippets.json?
Every time you called save-snippet, your server wrote the full snippet collection to this file using writeFileSync. If you stopped and restarted the server, loadSnippets() would read this file back into memory. This is how your server persists data between sessions.
list-snippets shows no results?
- If you restarted the Inspector between saving and listing, the server process was respawned. Check whether snippets.json exists in the file explorer. If it does, your snippets will load on the next server start.
- If snippets.json is missing, your save-snippet calls may have silently failed. Re-run save-snippet and check the response for error messages.
Help me figure out why my snippets aren't persisting.
Secret mission
Add Tagging and MCP Resources
Your snippet store works, but real code libraries need organization. In this secret mission, you'll add a tagging system so snippets can be categorized and filtered. Then you'll expose your entire snippet collection as MCP Resources, a second core MCP primitive that lets AI clients read your data directly without calling tools.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely inside StackBlitz WebContainers in your browser. There are no cloud services, paid APIs, or local installs to worry about.
Decide whether to keep your StackBlitz project for future reference or delete it entirely.
Resources you used:
- StackBlitz project (contains src/index.ts, package.json, tsconfig.json, snippets.json, and the dist/ folder).
Keep everything running
No action needed. Choose this if you plan to keep building on your MCP server or want to reference the code later.
- Your StackBlitz project persists in your dashboard automatically (if you are signed in).
- The snippets.json file stays in the project with any test snippets you saved.
- There are no ongoing costs. Everything lives in your browser session or StackBlitz account.
Pause - I'll come back to this later
Close the browser tab to free up memory. Your project stays saved in your StackBlitz dashboard for when you return.
- Close the StackBlitz browser tab.
- When you are ready to resume, open stackblitz.com and find your project in the dashboard.
Delete - I don't want to use this again
Remove the project from your StackBlitz account entirely. This cannot be undone.
- In the StackBlitz editor, open the Settings sidebar (the gear icon on the left).
- Scroll to the project-level settings and click the delete option to remove the project.
- Confirm the deletion when prompted.
Alternative: delete from the dashboard
You can also delete from your StackBlitz dashboard. Click the project logo to select it, then use the pop-up menu that appears and choose the delete option.
Nice Work!
Nice Work!
Nice work! You just built a fully functional MCP server from scratch using TypeScript and the official MCP SDK.
You've learned how to:
- Build a working MCP server that registers tools with Zod input schemas and returns structured responses to AI clients.
- Implement JSON-based file persistence so your server retains data across restarts.
- Test MCP tools interactively using the official MCP Inspector to verify inputs, outputs, and end-to-end behaviour.
- Secret Mission: Added a tagging system to snippets and exposed your snippet collection as MCP Resources, letting clients read data without calling tools.
Ready to quiz yourself?