MCP

Intent Surfaces

A reference manual for people who design and build MCP (Model Context Protocol) ecosystems

MCP

Intent Surfaces

A reference manual for people who design and build MCP (Model Context Protocol) ecosystems

MCP

Intent Surfaces

A reference manual for people who design and build MCP (Model Context Protocol) ecosystems

Your First Connection

Your First Connection

Your First Connection

The "Hello World" that actually does something • Building a bridge between Claude and your file system • That magical moment when it just works

The "Hello World" that actually does something • Building a bridge between Claude and your file system • That magical moment when it just works

The "Hello World" that actually does something • Building a bridge between Claude and your file system • That magical moment when it just works

The Moment Everything Clicks

The Moment Everything Clicks

Remember the first time you wrote code that actually worked? That little rush when "Hello World" appeared on your screen? With MCP, that moment is even better—because your first connection doesn't just print text. It gives AI actual capabilities. In the next 30 minutes, you'll build something real: an MCP server that lets Claude organize your downloads folder, search your notes, or analyze your code. No toy examples. No fictional scenarios. Real code that solves real problems.

Remember the first time you wrote code that actually worked? That little rush when "Hello World" appeared on your screen? With MCP, that moment is even better—because your first connection doesn't just print text. It gives AI actual capabilities. In the next 30 minutes, you'll build something real: an MCP server that lets Claude organize your downloads folder, search your notes, or analyze your code. No toy examples. No fictional scenarios. Real code that solves real problems.

The Moment

1
2
3
4
5
6
# What you'll need (all free and open source)
Prerequisites:
- Node.js 18 or higher (check with: node --version)
- A text editor (VS Code, Sublime, anything works)
- Claude Desktop or another MCP client
- 30 minutes and some curiosity

Starting Simple: The Note Keeper

Starting Simple: The Note Keeper

We'll build a personal note-taking server. Why? Because everyone takes notes, and having Claude help organize them is genuinely useful.

We'll build a personal note-taking server. Why? Because everyone takes notes, and having Claude help organize them is genuinely useful.

Note-server.js

Note-server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// note-server.js - Your first MCP server
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import fs from 'fs/promises';
import path from 'path';
import os from 'os';

// Where we'll store notes (in your home directory)
const NOTES_DIR = path.join(os.homedir(), '.mcp-notes');

// Create the notes directory if it doesn't exist
await fs.mkdir(NOTES_DIR, { recursive: true });

// Create an MCP server
const server = new Server(
  {
    name: 'note-keeper',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},  // We'll add tools here
    },
  }
);

// Tool 1: Create a new note
server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;
  
  switch (name) {
    case 'create_note': {
      const { title, content } = args;
      const filename = `${Date.now()}-${title.replace(/[^a-z0-9]/gi, '-')}.md`;
      const filepath = path.join(NOTES_DIR, filename);
      
      await fs.writeFile(filepath, `# ${title}\n\n${content}\n\nCreated: ${new Date().toISOString()}`);
      
      return {
        content: [{
          type: 'text',
          text: `Created note: ${title}`
        }]
      };
    }
    
    case 'list_notes': {
      const files = await fs.readdir(NOTES_DIR);
      const notes = files.filter(f => f.endsWith('.md'));
      
      return {
        content: [{
          type: 'text',
          text: `Found ${notes.length} notes:\n${notes.map(n => `- ${n}`).join('\n')}`
        }]
      };
    }
    
    case 'read_note': {
      const { filename } = args;
      const filepath = path.join(NOTES_DIR, filename);
      const content = await fs.readFile(filepath, 'utf-8');
      
      return {
        content: [{
          type: 'text',
          text: content
        }]
      };
    }
    
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

// List available tools
server.setRequestHandler('tools/list', async () => {
  return {
    tools: [
      {
        name: 'create_note',
        description: 'Create a new note',
        inputSchema: {
          type: 'object',
          properties: {
            title: { 
              type: 'string',
              description: 'Title of the note'
            },
            content: { 
              type: 'string',
              description: 'Content of the note'
            }
          },
          required: ['title', 'content']
        }
      },
      {
        name: 'list_notes',
        description: 'List all notes',
        inputSchema: {
          type: 'object',
          properties: {}
        }
      },
      {
        name: 'read_note',
        description: 'Read a specific note',
        inputSchema: {
          type: 'object',
          properties: {
            filename: {
              type: 'string',
              description: 'Filename of the note to read'
            }
          },
          required: ['filename']
        }
      }
    ]
  };
});

// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Note Keeper MCP Server running...');

visualization

6.1 Live Server Builder

6.1 Live Server Builder

Making It Real: Connect to Claude

Making It Real: Connect to Claude

Now for the magic moment—connecting your server to Claude Desktop:

Now for the magic moment—connecting your server to Claude Desktop:

Claude Desktop Log:

[2024-03-15 10:30:45]

Connecting to MCP servers...

[2024-03-15 10:30:45]

Connected to note-keeper

[2024-03-15 10:30:45]

Available tools: create_note, list_notes, read_note

[2024-03-15 10:30:46]

Ready!

Claude Desktop Log:

[2024-03-15 10:30:45]

Connecting to MCP servers...

[2024-03-15 10:30:45]

Connected to note-keeper

[2024-03-15 10:30:45]

Available tools: create_note, list_notes, read_note

[2024-03-15 10:30:46]

Ready!

Claude Desktop Log:

[2024-03-15 10:30:45]

Connecting to MCP servers...

[2024-03-15 10:30:45]

Connected to note-keeper

[2024-03-15 10:30:45]

Available tools: create_note, list_notes, read_note

[2024-03-15 10:30:46]

Ready!

6.2 Connection Visualizer

Your First Conversation

Your First Conversation

you

Hey Claude, can you help me keep track of some ideas?

Claude

I'd be happy to help you track ideas! I can see you have a note-keeping system

available. Would you like me to create a new note for your ideas?

you

Yes! Let's create a note about MCP project ideas.

Claude

I'll create that note for you right now.

[Claude uses the create_note tool]

Created note: MCP-project-ideas

Would you like to add some initial ideas to this note?

you

Add these:

1) Recipe organizer that reads from cooking websites

2) Email summarizer that creates daily digests

3) Code documentation generator


Claude

[Updates the note with your ideas]

Your ideas have been saved! You now have these MCP project concepts documented:

- Recipe organizer that reads from cooking websites

- Email summarizer that creates daily digests

- Code documentation generator

Would you like me to help you elaborate on any of these ideas?

you

Hey Claude, can you help me keep track of some ideas?

Claude

I'd be happy to help you track ideas! I can see you have a note-keeping system

available. Would you like me to create a new note for your ideas?

you

Yes! Let's create a note about MCP project ideas.

Claude

I'll create that note for you right now.

[Claude uses the create_note tool]

Created note: MCP-project-ideas

Would you like to add some initial ideas to this note?

you

Add these:

1) Recipe organizer that reads from cooking websites

2) Email summarizer that creates daily digests

3) Code documentation generator


Claude

[Updates the note with your ideas]

Your ideas have been saved! You now have these MCP project concepts documented:

- Recipe organizer that reads from cooking websites

- Email summarizer that creates daily digests

- Code documentation generator

Would you like me to help you elaborate on any of these ideas?

you

Hey Claude, can you help me keep track of some ideas?

Claude

I'd be happy to help you track ideas! I can see you have a note-keeping system

available. Would you like me to create a new note for your ideas?

you

Yes! Let's create a note about MCP project ideas.

Claude

I'll create that note for you right now.

[Claude uses the create_note tool]

Created note: MCP-project-ideas

Would you like to add some initial ideas to this note?

you

Add these:

1) Recipe organizer that reads from cooking websites

2) Email summarizer that creates daily digests

3) Code documentation generator


Claude

[Updates the note with your ideas]

Your ideas have been saved! You now have these MCP project concepts documented:

- Recipe organizer that reads from cooking websites

- Email summarizer that creates daily digests

- Code documentation generator

Would you like me to help you elaborate on any of these ideas?

6.3 Conversation Simulator

6.3 Conversation Simulator

Level Up: Adding Search

Level Up: Adding Search

Let's make our server more useful by adding search:

Let's make our server more useful by adding search:

Search Tools

Search Tools

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// Add this tool to your server
case 'search_notes': {
  const { query } = args;
  const files = await fs.readdir(NOTES_DIR);
  const results = [];
  
  for (const file of files) {
    if (!file.endsWith('.md')) continue;
    
    const content = await fs.readFile(
      path.join(NOTES_DIR, file), 
      'utf-8'
    );
    
    // Simple search - check if query appears in content
    if (content.toLowerCase().includes(query.toLowerCase())) {
      // Extract title from content
      const titleMatch = content.match(/^# (.+)$/m);
      const title = titleMatch ? titleMatch[1] : file;
      
      // Find the line containing the query
      const lines = content.split('\n');
      const matchingLine = lines.find(line => 
        line.toLowerCase().includes(query.toLowerCase())
      );
      
      results.push({
        filename: file,
        title,
        excerpt: matchingLine || 'Match found in file',
      });
    }
  }
  
  return {
    content: [{
      type: 'text',
      text: results.length > 0 
        ? `Found ${results.length} matches:\n\n${results.map(r => 
            `**${r.title}** (${r.filename})\n> ${r.excerpt}`
          ).join('\n\n')}`
        : `No notes found matching "${query}"`
    }]
  };
}

// Don't forget to add it to tools/list:
{
  name: 'search_notes',
  description: 'Search through all notes',
  inputSchema: {
    type: 'object',
    properties: {
      query: {
        type: 'string',
        description: 'Search term to look for in notes'
      }
    },
    required: ['query']
  }
}

you

Find my notes about Python

Claude

Let me search your notes for "Python".

[Claude uses search_notes tool]

Found 3 matches:

Learning Python

Started learning Python basics today

1709823456-Learning-Python.md

Project Ideas

Build a Python script to organize photos

1709834567-Project-Ideas.md

Book List

"Automate the Boring Stuff with Python" - recommended by Sarah

1709845678-Book-List.md

you

Find my notes about Python

Claude

Let me search your notes for "Python".

[Claude uses search_notes tool]

Found 3 matches:

Learning Python

Started learning Python basics today

1709823456-Learning-Python.md

Project Ideas

Build a Python script to organize photos

1709834567-Project-Ideas.md

Book List

"Automate the Boring Stuff with Python" - recommended by Sarah

1709845678-Book-List.md

you

Find my notes about Python

Claude

Let me search your notes for "Python".

[Claude uses search_notes tool]

Found 3 matches:

Learning Python

Started learning Python basics today

1709823456-Learning-Python.md

Project Ideas

Build a Python script to organize photos

1709834567-Project-Ideas.md

Book List

"Automate the Boring Stuff with Python" - recommended by Sarah

1709845678-Book-List.md

6.4 Feature Workshop

6.4 Feature Workshop

Real-World Example: The Download Organizer

Real-World Example: The Download Organizer

Let's build something everyone needs—a download folder organizer:

Let's build something everyone needs—a download folder organizer:

Download Organizer Code

Download Organizer Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import fs from 'fs/promises';
import path from 'path';
import os from 'os';

const DOWNLOADS_DIR = path.join(os.homedir(), 'Downloads');

// File type categories
const FILE_CATEGORIES = {
  images: ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp'],
  documents: ['.pdf', '.doc', '.docx', '.txt', '.odt'],
  videos: ['.mp4', '.avi', '.mkv', '.mov', '.wmv'],
  audio: ['.mp3', '.wav', '.flac', '.aac', '.ogg'],
  archives: ['.zip', '.rar', '.7z', '.tar', '.gz'],
  code: ['.js', '.py', '.html', '.css', '.json', '.xml'],
};

const server = new Server(
  {
    name: 'download-organizer',
    version: '1.0.0',
  },
  {
    capabilities: {
      tools: {},
    },
  }
);

// Analyze downloads folder
async function analyzeDownloads() {
  const files = await fs.readdir(DOWNLOADS_DIR);
  const analysis = {
    total: files.length,
    byCategory: {},
    oldestFile: null,
    largestFile: null,
    totalSize: 0,
  };
  
  for (const category of Object.keys(FILE_CATEGORIES)) {
    analysis.byCategory[category] = 0;
  }
  analysis.byCategory.other = 0;
  
  for (const file of files) {
    const filepath = path.join(DOWNLOADS_DIR, file);
    const stat = await fs.stat(filepath);
    
    if (!stat.isFile()) continue;
    
    // Categorize file
    const ext = path.extname(file).toLowerCase();
    let categorized = false;
    
    for (const [category, extensions] of Object.entries(FILE_CATEGORIES)) {
      if (extensions.includes(ext)) {
        analysis.byCategory[category]++;
        categorized = true;
        break;
      }
    }
    
    if (!categorized) {
      analysis.byCategory.other++;
    }
    
    // Track statistics
    analysis.totalSize += stat.size;
    
    if (!analysis.oldestFile || stat.mtime < analysis.oldestFile.date) {
      analysis.oldestFile = { name: file, date: stat.mtime };
    }
    
    if (!analysis.largestFile || stat.size > analysis.largestFile.size) {
      analysis.largestFile = { name: file, size: stat.size };
    }
  }
  
  return analysis;
}

// Tool implementations
server.setRequestHandler('tools/call', async (request) => {
  const { name, arguments: args } = request.params;
  
  switch (name) {
    case 'analyze_downloads': {
      const analysis = await analyzeDownloads();
      
      const report = `# Downloads Folder Analysis

Total files: ${analysis.total}

By Category

By Category

1
2
3
${Object.entries(analysis.byCategory)
  .map(([cat, count]) => `- ${cat}: ${count} files`)
  .join('\n')}

Statistics

Statistics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
- Total size: ${(analysis.totalSize / 1024 / 1024).toFixed(2)} MB
- Oldest file: ${analysis.oldestFile?.name || 'None'} (${analysis.oldestFile?.date.toLocaleDateString() || 'N/A'})
- Largest file: ${analysis.largestFile?.name || 'None'} (${((analysis.largestFile?.size || 0) / 1024 / 1024).toFixed(2)} MB)`;
      
      return {
        content: [{
          type: 'text',
          text: report
        }]
      };
    }
    
    case 'organize_downloads': {
      const { dryRun = true } = args;
      const moves = [];
      
      const files = await fs.readdir(DOWNLOADS_DIR);
      
      for (const file of files) {
        const filepath = path.join(DOWNLOADS_DIR, file);
        const stat = await fs.stat(filepath);
        
        if (!stat.isFile()) continue;
        
        const ext = path.extname(file).toLowerCase();
        
        for (const [category, extensions] of Object.entries(FILE_CATEGORIES)) {
          if (extensions.includes(ext)) {
            const targetDir = path.join(DOWNLOADS_DIR, category);
            const targetPath = path.join(targetDir, file);
            
            moves.push({
              from: file,
              to: `${category}/${file}`,
              category
            });
            
            if (!dryRun) {
              await fs.mkdir(targetDir, { recursive: true });
              await fs.rename(filepath, targetPath);
            }
            
            break;
          }
        }
      }
      
      const summary = dryRun 
        ? `Would organize ${moves.length} files:\n${moves.map(m => `- ${m.from} → ${m.to}`).join('\n')}`
        : `Organized ${moves.length} files into categories`;
      
      return {
        content: [{
          type: 'text',
          text: summary
        }]
      };
    }
    
    case 'find_old_downloads': {
      const { days = 30 = args;
      const cutoffDate = new Date();
      cutoffDate.setDate(cutoffDate.getDate() - days);
      
      const files = await fs.readdir(DOWNLOADS_DIR);
      const oldFiles = [];
      
      for (const file of files) {
        const filepath = path.join(DOWNLOADS_DIR, file);
        const stat = await fs.stat(filepath);
        
        if (stat.isFile() && stat.mtime < cutoffDate) {
          oldFiles.push({
            name: file,
            date: stat.mtime,
            size: stat.size,
            ageInDays: Math.floor((Date.now() - stat.mtime) / (1000 * 60 * 60 * 24))
          });
        }
      }
      
      oldFiles.sort((a, b) => a.date - b.date);
      
      const report = oldFiles.length > 0
        ? `Found ${oldFiles.length} files older than ${days} days:\n\n${oldFiles.map(f => 
            `- ${f.name} (${f.ageInDays} days old, ${(f.size / 1024).toFixed(1)} KB)`
          ).join('\n')}`
        : `No files older than ${days} days found`;
      
      return {
        content: [{
          type: 'text',
          text: report
        }]
      };
    }
    
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
});

// Tool definitions
server.setRequestHandler('tools/list', async () => {
  return {
    tools: [
      {
        name: 'analyze_downloads',
        description: 'Analyze the contents of your Downloads folder',
        inputSchema: {
          type: 'object',
          properties: {}
        }
      },
      {
        name: 'organize_downloads',
        description: 'Organize downloads into folders by file type',
        inputSchema: {
          type: 'object',
          properties: {
            dryRun: {
              type: 'boolean',
              description: 'If true, show what would be done without moving files',
              default: true
            }
          }
        }
      },
      {
        name: 'find_old_downloads',
        description: 'Find files older than specified days',
        inputSchema: {
          type: 'object',
          properties: {
            days: {
              type: 'number',
              description: 'Files older than this many days',
              default: 30
            }
          }
        }
      }
    ]
  };
});

const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Download Organizer MCP Server running...');

Now you can have conversations like:

Now you can have conversations like:

you

My downloads folder is a mess. Can you help?

Claude

I'll help you analyze and organize your downloads folder. Let me first

see what's in there.

[Claude uses analyze_downloads]

Downloads Folder Analysis

Total files: 247

-- By Category --

- images: 89 files

- documents: 43 files

- videos: 12 files

- audio: 23 files

- archives: 31 files

- code: 15 files

- other: 34 files

-- Statistics --

- Total size: 3,421.56 MB

- Oldest file: old-presentation.ppt (1/15/2023)

- Largest file: conference-recording.mp4 (892.34 MB)

You have quite a collection!

Would you like me to:

1.Organize these files into category folders?

2. Find old files you might want to clean up?

3. Both?

you

Let's find files older than 60 days first

Claude

[Claude uses find_old_downloads with days=60]

Found 73 files older than 60 days:

Old-presentation.ppt (423 days old, 2,341.2 KB)

Tax-docs-2023.zip (287 days old, 15,234.1 KB)

Meeting-notes-jan.pdf (198 days old, 234.5 KB)

(showing first few, 70 more)

These files are taking up about 1.2 GB.

Would you like me to:

- Move them to an "Archive" folder?

- See the complete list?

- organize your current files by type?

you

My downloads folder is a mess. Can you help?

Claude

I'll help you analyze and organize your downloads folder. Let me first

see what's in there.

[Claude uses analyze_downloads]

Downloads Folder Analysis

Total files: 247

-- By Category --

- images: 89 files

- documents: 43 files

- videos: 12 files

- audio: 23 files

- archives: 31 files

- code: 15 files

- other: 34 files

-- Statistics --

- Total size: 3,421.56 MB

- Oldest file: old-presentation.ppt (1/15/2023)

- Largest file: conference-recording.mp4 (892.34 MB)

You have quite a collection!

Would you like me to:

1.Organize these files into category folders?

2. Find old files you might want to clean up?

3. Both?

you

Let's find files older than 60 days first

Claude

[Claude uses find_old_downloads with days=60]

Found 73 files older than 60 days:

Old-presentation.ppt (423 days old, 2,341.2 KB)

Tax-docs-2023.zip (287 days old, 15,234.1 KB)

Meeting-notes-jan.pdf (198 days old, 234.5 KB)

(showing first few, 70 more)

These files are taking up about 1.2 GB.

Would you like me to:

- Move them to an "Archive" folder?

- See the complete list?

- organize your current files by type?

you

My downloads folder is a mess. Can you help?

Claude

I'll help you analyze and organize your downloads folder. Let me first

see what's in there.

[Claude uses analyze_downloads]

Downloads Folder Analysis

Total files: 247

-- By Category --

- images: 89 files

- documents: 43 files

- videos: 12 files

- audio: 23 files

- archives: 31 files

- code: 15 files

- other: 34 files

-- Statistics --

- Total size: 3,421.56 MB

- Oldest file: old-presentation.ppt (1/15/2023)

- Largest file: conference-recording.mp4 (892.34 MB)

You have quite a collection!

Would you like me to:

1.Organize these files into category folders?

2. Find old files you might want to clean up?

3. Both?

you

Let's find files older than 60 days first

Claude

[Claude uses find_old_downloads with days=60]

Found 73 files older than 60 days:

Old-presentation.ppt (423 days old, 2,341.2 KB)

Tax-docs-2023.zip (287 days old, 15,234.1 KB)

Meeting-notes-jan.pdf (198 days old, 234.5 KB)

(showing first few, 70 more)

These files are taking up about 1.2 GB.

Would you like me to:

- Move them to an "Archive" folder?

- See the complete list?

- organize your current files by type?

Images

Documents

Videos

Images

Documents

Videos

6.5 Downloads Dashboard

6.5 Downloads Dashboard

Debugging: When Things Don't Work

Debugging: When Things Don't Work

Let's be honest—things don't always work the first time. Here's how to debug MCP connections:

Let's be honest—things don't always work the first time. Here's how to debug MCP connections:

Debug Environment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Add logging to your server for debugging
const DEBUG = process.env.DEBUG === 'true';

function log(...args) {
  if (DEBUG) {
    console.error('[DEBUG]', new Date().toISOString(), ...args);
  }
}

server.setRequestHandler('tools/call', async (request) => {
  log('Tool call received:', request.params.name);
  
  try {
    // Your tool implementation
    const result = await handleTool(request);
    log('Tool call successful:', request.params.name);
    return result;
  } catch (error) {
    log('Tool call failed:', request.params.name, error);
    throw error;
  }
});

// Run with debugging:
// DEBUG=true node your-server.js

Common Issues and Fixes

Common Issues and Fixes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# Issue 1: "Connection refused"
# Fix: Check your path in claude_desktop_config.json
{
  "mcpServers": {
    "your-server": {
      "command": "node",
      "args": ["/absolute/path/to/your-server.js"]  # Must be absolute!
    }
  }
}

# Issue 2: "Module not found"
# Fix: Install dependencies
cd your-server-directory
npm install @modelcontextprotocol/sdk

# Issue 3: "Permission denied"  
# Fix: Make your script executable
chmod +x your-server.js

# Issue 4: Server crashes silently
# Fix: Add error handling
process.on('uncaughtException', (error) => {
  console.error('Uncaught exception:', error);
  process.exit(1);
});

Beyond Basic: Adding Resources

Beyond Basic: Adding Resources

So far we've used Tools (actions). Let's add Resources (data that Claude can read):

So far we've used Tools (actions). Let's add Resources (data that Claude can read):

Add Resources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Add to your note server
server.setRequestHandler('resources/list', async () => {
  return {
    resources: [
      {
        uri: 'notes://recent',
        name: 'Recent Notes',
        description: 'Notes from the last 7 days',
        mimeType: 'text/plain'
      }
    ]
  };
});

server.setRequestHandler('resources/read', async (request) => {
  const { uri } = request.params;
  
  if (uri === 'notes://recent') {
    const files = await fs.readdir(NOTES_DIR);
    const recentNotes = [];
    const cutoff = Date.now() - (7 * 24 * 60 * 60 * 1000); // 7 days
    
    for (const file of files) {
      if (!file.endsWith('.md')) continue;
      
      const filepath = path.join(NOTES_DIR, file);
      const stat = await fs.stat(filepath);
      
      // Check if file was created in last 7 days
      const timestamp = parseInt(file.split('-')[0]);
      if (timestamp > cutoff) {
        const content = await fs.readFile(filepath, 'utf-8');
        recentNotes.push(content);
      }
    }
    
    return {
      contents: [{
        uri: uri,
        mimeType: 'text/plain',
        text: recentNotes.join('\n\n---\n\n')
      }]
    };
  }
  
  throw new Error(`Unknown resource: ${uri}`);
});

Now Claude can proactively reference your recent notes:

Now Claude can proactively reference your recent notes:

you

What have I been working on lately?

Claude

Let me check your recent notes to see what you've been working on.

[Claude reads the notes://recent resource]

Based on your recent notes, you've been working on several things:

MCP Integration Project

3 days ago

You're building a note-keeping system with search functionality

Learning Rust

5 days ago

Planning a trip to Portland with a list of restaurants and hiking trails

Book Reading List

6 days ago

Added "The Pragmatic Programmer" and

"Designing Data-Intensive Applications"

Would you like me to help you continue with any of these projects?

you

What have I been working on lately?

Claude

Let me check your recent notes to see what you've been working on.

[Claude reads the notes://recent resource]

Based on your recent notes, you've been working on several things:

MCP Integration Project

3 days ago

You're building a note-keeping system with search functionality

Learning Rust

5 days ago

Planning a trip to Portland with a list of restaurants and hiking trails

Book Reading List

6 days ago

Added "The Pragmatic Programmer" and

"Designing Data-Intensive Applications"

Would you like me to help you continue with any of these projects?

you

What have I been working on lately?

Claude

Let me check your recent notes to see what you've been working on.

[Claude reads the notes://recent resource]

Based on your recent notes, you've been working on several things:

MCP Integration Project

3 days ago

You're building a note-keeping system with search functionality

Learning Rust

5 days ago

Planning a trip to Portland with a list of restaurants and hiking trails

Book Reading List

6 days ago

Added "The Pragmatic Programmer" and

"Designing Data-Intensive Applications"

Would you like me to help you continue with any of these projects?

Tools

Resources

Tools

Resources

Tools

Resources

6.6 Conversation Simulator

The Power of Composition

The Power of Composition

The real magic happens when you combine MCP servers:

The real magic happens when you combine MCP servers:

Bash Composition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Your claude_desktop_config.json can have multiple servers
{
  "mcpServers": {
    "note-keeper": {
      "command": "node",
      "args": ["/path/to/note-server.js"]
    },
    "download-organizer": {
      "command": "node", 
      "args": ["/path/to/download-organizer.js"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "your-token"
      }
    }
  }
}

Now Claude can proactively reference your recent notes:

Now Claude can proactively reference your recent notes:

you

Can you check my GitHub PRs and create a note about what I need to review?

Claude

I'll check your GitHub PRs and create a summary note for you.

[Claude uses GitHub server to list PRs]

[Claude uses note-keeper to create a note]

I've created a note titled "GitHub-PR-Review-2024-03-15" with the following PRs that need your attention:

PR #234: Update authentication flow

Waiting for your review

Last updated: 2 hours ago

3 files changed, 2 comments

PR #229: Fix memory leak in worker process

Approved by TeamLead, waiting for your approval

Last updated: Yesterday

5 files changed

The note has been saved to your notes system. Would you like me to analyze the changes in any of these PRs?

you

Can you check my GitHub PRs and create a note about what I need to review?

Claude

I'll check your GitHub PRs and create a summary note for you.

[Claude uses GitHub server to list PRs]

[Claude uses note-keeper to create a note]

I've created a note titled "GitHub-PR-Review-2024-03-15" with the following PRs that need your attention:

PR #234: Update authentication flow

Waiting for your review

Last updated: 2 hours ago

3 files changed, 2 comments

PR #229: Fix memory leak in worker process

Approved by TeamLead, waiting for your approval

Last updated: Yesterday

5 files changed

The note has been saved to your notes system. Would you like me to analyze the changes in any of these PRs?

you

Can you check my GitHub PRs and create a note about what I need to review?

Claude

I'll check your GitHub PRs and create a summary note for you.

[Claude uses GitHub server to list PRs]

[Claude uses note-keeper to create a note]

I've created a note titled "GitHub-PR-Review-2024-03-15" with the following PRs that need your attention:

PR #234: Update authentication flow

Waiting for your review

Last updated: 2 hours ago

3 files changed, 2 comments

PR #229: Fix memory leak in worker process

Approved by TeamLead, waiting for your approval

Last updated: Yesterday

5 files changed

The note has been saved to your notes system. Would you like me to analyze the changes in any of these PRs?

6.7 Server Composer

Your MCP Toolkit

Your MCP Toolkit

Here's a practical toolkit for building MCP servers:

Here's a practical toolkit for building MCP servers:

mcp-toolkit.js

mcp-toolkit.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// mcp-toolkit.js - Reusable utilities

// 1. Error handling wrapper
export function withErrorHandling(handler) {
  return async (request) => {
    try {
      return await handler(request);
    } catch (error) {
      console.error('Error in handler:', error);
      return {
        content: [{
          type: 'text',
          text: `Error: ${error.message}`
        }]
      };
    }
  };
}

// 2. Input validation
export function validateInput(schema, input) {
  for (const [key, rules] of Object.entries(schema)) {
    if (rules.required && !(key in input)) {
      throw new Error(`Missing required field: ${key}`);
    }
    
    if (key in input && rules.type) {
      const actualType = typeof input[key];
      if (actualType !== rules.type) {
        throw new Error(`${key} must be ${rules.type}, got ${actualType}`);
      }
    }
  }
  return true;
}

// 3. Pagination helper
export function paginate(items, page = 1, perPage = 10) {
  const start = (page - 1) * perPage;
  const end = start + perPage;
  
  return {
    items: items.slice(start, end),
    totalItems: items.length,
    totalPages: Math.ceil(items.length / perPage),
    currentPage: page,
    hasNext: end < items.length,
    hasPrev: page > 1
  };
}

// 4. Rate limiting
export class RateLimiter {
  constructor(maxRequests = 10, windowMs = 60000) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
    this.requests = new Map();
  }
  
  check(clientId) {
    const now = Date.now();
    const userRequests = this.requests.get(clientId) || [];
    
    // Remove old requests
    const validRequests = userRequests.filter(
      time => now - time < this.windowMs
    );
    
    if (validRequests.length >= this.maxRequests) {
      throw new Error('Rate limit exceeded');
    }
    
    validRequests.push(now);
    this.requests.set(clientId, validRequests);
    return true;
  }
}

// 5. Simple caching
export class Cache {
  constructor(ttlMs = 300000) { // 5 minutes default
    this.cache = new Map();
    this.ttlMs = ttlMs;
  }
  
  get(key) {
    const entry = this.cache.get(key);
    if (!entry) return null;
    
    if (Date.now() - entry.timestamp > this.ttlMs) {
      this.cache.delete(key);
      return null;
    }
    
    return entry.value;
  }
  
  set(key, value) {
    this.cache.set(key, {
      value,
      timestamp: Date.now()
    });
  }
}

6.8 Conversation Simulator

6.8 Conversation Simulator

What You've Built

What You've Built

Take a moment to appreciate what you've accomplished:

Take a moment to appreciate what you've accomplished:

Built a working MCP server that solves real problems

Connected it to Claude Desktop and had actual conversations

Added search capabilities to make it more useful

Created a download organize that can clean up your files

Learned debugging techniques for when things go wrong

Understand Resources vs Tools and when to use each

Can combine multiple servers for complex workflows

Next Steps: Where to Go From Here

Next Steps: Where to Go From Here

Here's a practical toolkit for building MCP servers:

Here's a practical toolkit for building MCP servers:

mcp-toolkit.js

mcp-toolkit.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Your journey continues...

const nextProjects = [
  {
    name: "Email Digest Server",
    difficulty: "Medium",
    description: "Read emails and create daily summaries",
    learnings: ["Email APIs", "Scheduling", "Summarization"]
  },
  {
    name: "Code Analyzer", 
    difficulty: "Medium",
    description: "Analyze code quality and suggest improvements",
    learnings: ["AST parsing", "Code metrics", "Best practices"]
  },
  {
    name: "Personal Finance Tracker",
    difficulty: "Hard",
    description: "Track expenses and generate reports",
    learnings: ["Data persistence", "Calculations", "Visualizations"]
  },
  {
    name: "Smart Home Controller",
    difficulty: "Hard", 
    description: "Control IoT devices through Claude",
    learnings: ["IoT protocols", "Device APIs", "Security"]
  }
];

// Join the community
const resources = {
  documentation: "https://modelcontextprotocol.io/docs",
  github: "https://github.com/modelcontextprotocol",
  discord: "MCP Community Discord",
  examples: "https://github.com/modelcontextprotocol/servers"
};

YOU ARE HERE NOW!

YOU ARE HERE NOW!

YOU ARE HERE NOW!

6.9 Your MCP Journey Map

6.9 Your MCP Journey Map