#
tokens: 5902/50000 9/9 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── juce-docs-mcp-server
│   ├── .gitignore
│   ├── package-lock.json
│   ├── package.json
│   ├── README-DEV.md
│   ├── README.md
│   ├── src
│   │   ├── index.ts
│   │   ├── juce-docs.ts
│   │   └── test-client.ts
│   └── tsconfig.json
├── LICENSE
└── README.md
```

# Files

--------------------------------------------------------------------------------
/juce-docs-mcp-server/.gitignore:
--------------------------------------------------------------------------------

```
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build outputs
dist/
build/
*.js.map

# TypeScript cache
*.tsbuildinfo

# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
.DS_Store

# Environment variables
.env
.env.local
.env.*.local

# Logs
logs/
*.log 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# mcp-servers-jos

[Model Context
Protocol](https://modelcontextprotocol.io/tutorials/building-mcp-with-llms)
(MCP) servers by [JOS](https://github.com/josmithiii), starting from
an [MCP Template](https://github.com/josmithiii/mcp-template)

## Contents

* [juce-docs-mcp-server](./juce-docs-mcp-server/README.md) - access JUCE Framework C++ class documentation


```

--------------------------------------------------------------------------------
/juce-docs-mcp-server/README.md:
--------------------------------------------------------------------------------

```markdown
# JUCE Documentation MCP Server

An MCP (Model Context Protocol) server that provides access to JUCE Framework C++ class documentation.

## Features

- Fetch documentation for specific JUCE classes
- List all available JUCE classes
- Search for classes by name
- Format documentation as markdown
- Expose documentation through MCP resources and tools

## Installation

```bash
# Clone the repository
git clone https://github.com/josmithiii/mcp-servers-jos.git
cd mcp-servers-jos/juce-docs-mcp-server

# Install dependencies
npm install

# Build the project
npm run build
```

## Usage

### Running the Server

```bash
npm start
```

This starts the MCP server using `stdio` as the transport mechanism, which allows it to be used with MCP clients like Claude Desktop App, Continue, or other MCP-compatible applications.

### Adding the MCP service to Cursor (tested 2025-03-11)

1. Open Cursor / Settings / Cursor Settings
2. Select MCP
3. Set the `Name` to JUCE Docs (or whatever), and set the `Type` to `Command`
3. Set the `Command` to `node /path/to/juce-docs-mcp-server/dist/index.js`,
   replacing `/path/to/juce-docs-mcp-server` with the actual path into your clone
5. Restart Cursor to apply the changes (it will internally run `node .../dist/index.js`)

Note that Cursor sends MCP requests to _your local server_ that you started with `npm start` above.

### Available Resources

- `juce://class/{className}` - Get documentation for a specific JUCE class
- `juce://classes` - List all available JUCE classes

### Available Tools

- `/search-juce-classes` - Search for JUCE classes by name
- `/get-juce-class-docs` - Get documentation for a specific JUCE class

### Available Prompts

- `explore-juce` - Interactive exploration of JUCE framework components
  - Use without arguments for an overview of main components
  - Add a topic to explore specific functionality (e.g., `explore-juce audio`)

### Resources and Tools

In addition to prompts that direct your LLM (such as in Cursor) to use
the MCP internally, you can also query it directly via "resource" and
"tool" names:

1. **Resources** look like URLs that directly fetch specific content. They
   follow a URI-like pattern with the format `protocol://path`. These
   are defined in the server as direct resource endpoints.  Example:
   `juce://classes`

2. **Tools** use names beginning with `/` and support a following
   argument, i.e., `/tool-name arg-string`, and provide interactive
   commands that perform an action. MCP tools start with `/` to
   distinguish them from resources. This is similar to how slash
   commands work in many applications such as `Claude Code` or
   `aider`.  Note that in an IDE chat, the `arg-string` can include
   spaces and is terminated by end-of-line (according to Claude 3.7).

In summary, when connected to an MCP client (such as via Cursor chat),
you can access "resources" in the format `protocol://path` and "tools"
in the format `/tool-name arg string`.

## Examples

1. List all available classes: `juce://classes`
2. Get documentation for a specific class: `juce://class/ValueTree`
3. Search for all Audio classes: `/search-juce-classes Audio`
4. Get documentation for specific classes: `/get-juce-class-docs AudioProcessor`

## Changing the JUCE Doc URL

In `juce-docs-mcp-server/src/juce-docs.ts`, edit the line
 ```
 const BASE_URL = 'https://ccrma.stanford.edu/~jos/juce_modules';
 ```
More up-to-date possibilities include 
 ```
 const BASE_URL = 'https://docs.juce.com/develop';
 const BASE_URL = 'https://docs.juce.com/master';
 ```

## Tips for Effective JUCE Development

When working on a JUCE project, here's how to get the most out of the JUCE Documentation MCP Server:

### Quick Reference Workflows

1. **Exploring Components**
   - Start with `/search-juce-classes` followed by a general category (Audio, GUI, etc.)
   - Use `explore-juce audio` (or other domain) to get an overview of related classes

2. **Implementation Help**
   - When implementing a specific feature, use `juce://class/ClassName` to get detailed documentation
   - Look for code examples in the class documentation

3. **Method Reference**
   - The class documentation includes all methods with signatures and descriptions
   - Use this when you need to understand parameter types or return values

### Integration with Your Development Process

1. **Keep Cursor Open Alongside Your IDE**
   - Have Cursor with the MCP server running in a separate window
   - This gives you instant access to documentation without leaving your code editor

2. **Use During Planning Phases**
   - Before implementing a feature, explore available classes with `/search-juce-classes`
   - This helps you understand the JUCE approach before writing code

3. **Debugging Assistance**
   - When encountering unexpected behavior, check the class documentation
   - Look for notes about edge cases or implementation details

### Specific JUCE Development Tips

1. **Audio Processing**
   - Start with `AudioProcessor` for plugin development
   - Use `AudioSource` for playback applications
   - Check `dsp::` namespace classes for efficient signal processing

2. **GUI Development**
   - Base all custom components on the `Component` class
   - Use `AudioAppComponent` to combine audio and GUI functionality
   - Look at `LookAndFeel` for styling

3. **Plugin Development**
   - Reference `AudioProcessor` and `AudioProcessorEditor` for the core plugin architecture
   - Check `AudioProcessorValueTreeState` for parameter management

## Implementation Details

The server fetches documentation from the JUCE documentation hosted at Stanford CCRMA
(https://ccrma.stanford.edu/~jos/juce_modules/), but of course you can change that, as noted above.
It processes the HTML documentation in real-time:

1. Class list is fetched from the annotated class list page
2. Individual class documentation is parsed from class-specific pages
3. Documentation is formatted as markdown for consistent display
4. Results are cached in memory during server runtime

## Error Handling

Common issues and solutions:

1. **Class Not Found**: If a class name is invalid or not found, the server will return a clear error message
2. **Connection Issues**: If the JUCE documentation site is unreachable, check your internet connection
3. **Server Start Failure**: Ensure the correct Node.js version is installed and the build step completed successfully
4. **Cursor Integration**: If the server isn't working in Cursor, verify the command path in MCP settings is correct

## Development

```bash
# Run in development mode with auto-recompilation
npm run dev
```

[Developer Notes](./README-DEV.md)

## License

MIT

```

--------------------------------------------------------------------------------
/juce-docs-mcp-server/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist",
    "sourceMap": true,
    "declaration": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
} 
```

--------------------------------------------------------------------------------
/juce-docs-mcp-server/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "juce-docs-mcp-server",
  "version": "1.0.0",
  "description": "MCP server for JUCE Framework documentation",
  "type": "module",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc -w",
    "test": "tsc && node dist/test-client.js"
  },
  "keywords": [
    "mcp",
    "juce",
    "documentation"
  ],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.6.1",
    "cheerio": "^1.0.0-rc.12",
    "node-fetch": "^2.6.9",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/node": "^20.10.5",
    "@types/node-fetch": "^2.6.9",
    "typescript": "^5.3.3"
  }
}

```

--------------------------------------------------------------------------------
/juce-docs-mcp-server/README-DEV.md:
--------------------------------------------------------------------------------

```markdown
# JUCE Documentation MCP Server - Development Notes

Development History and Technical Details

## Project Genesis

The project started with the following prompt in Cursor while editing
a fresh clone of the [MCP Template](https://github.com/josmithiii/mcp-template.git):

> I want to create an MCP server which provides an API for retrieving C++ class documentation for the JUCE Framework, using this URL for the lookups:
> https://ccrma.stanford.edu/~jos/juce_modules/index.html<br/>
> For example, documentation for the ValueTree class can be accessed as<br/>
> https://ccrma.stanford.edu/~jos/juce_modules/classValueTree.html

That's it!  (However, the template includes a couple of important files that guide the generation.)

## Architecture Overview

The key components of the project are:

1. **juce-docs.ts**: Contains utility functions for fetching and parsing JUCE documentation from the Stanford CCRMA website.
   - Handles HTML parsing and markdown conversion
   - Manages class list fetching and caching
   - Provides search functionality

2. **index.ts**: The main MCP server implementation that exposes:
   - Resources (`juce://class/{className}`, `juce://classes`)
   - Tools (`search-classes`, `get-class-docs`)
   - Prompts (`explore-juce`)

3. **test-client.ts**: A test client that verifies the server functionality
   - Tests resource endpoints
   - Tests tool invocations
   - Tests prompt handling

## Implementation Notes

### HTML Parsing Strategy

The server parses Doxygen-generated HTML documentation:
1. Class list is extracted from the annotated list page
2. Individual class pages are parsed for detailed documentation
3. HTML is converted to markdown for better display in LLM clients

### Caching

- Class list is cached in memory during server runtime
- Individual class documentation is fetched on demand
- No persistent caching between server restarts (currently)

### Error Handling Strategy

The server implements robust error handling:
1. Network request timeouts and retries
2. Invalid class name validation
3. HTML parsing error recovery
4. Clear error messages for client display

## Development Workflow

1. Make changes to source files
2. Run `npm run dev` for development with auto-recompilation
3. Test changes using the test client
4. Build with `npm run build` for production

## Future Improvements

Potential enhancements to consider:

1. Persistent caching of documentation
2. Support for method-level documentation lookup
3. Integration with other documentation sources
4. Enhanced search capabilities (e.g., method search)
5. Support for code examples and snippets

## Project Status Summary

The MCP server is working correctly and provides:

1. Access to JUCE Framework C++ class documentation
2. Resources for retrieving specific class documentation
3. Tools for searching and exploring classes
4. A prompt for guided framework exploration
5. Verified functionality through test client

The server successfully makes JUCE documentation directly accessible from LLM applications, enhancing the development workflow when working with the JUCE Framework. 

```

--------------------------------------------------------------------------------
/juce-docs-mcp-server/src/test-client.ts:
--------------------------------------------------------------------------------

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { spawn } from "child_process";

async function main() {
  try {
    console.log("Starting test client for JUCE Documentation MCP Server...");
    
    // Start the server process
    const serverProcess = spawn("node", ["dist/index.js"], {
      stdio: ["pipe", "pipe", process.stderr]
    });
    
    // Create a transport that communicates with the server process
    const transport = new StdioClientTransport({
      command: "node",
      args: ["dist/index.js"]
    });
    
    // Create an MCP client
    const client = new Client(
      {
        name: "JUCE Docs Test Client",
        version: "1.0.0"
      },
      {
        capabilities: {
          resources: {},
          tools: {}
        }
      }
    );
    
    // Connect to the server
    await client.connect(transport);
    console.log("Connected to server");
    
    // Test listing resources
    console.log("\nListing resources...");
    const resources = await client.listResources();
    console.log("Available resources:", resources);
    
    // Test listing tools
    console.log("\nListing tools...");
    const tools = await client.listTools();
    console.log("Available tools:", tools);
    
    // Test reading the class list resource
    console.log("\nReading class list...");
    const classList = await client.readResource({ uri: "juce://classes" });
    if (classList.contents && classList.contents[0] && classList.contents[0].text) {
      const text = classList.contents[0].text as string;
      console.log("Class list:", text.substring(0, 200) + "...");
    }
    
    // Test reading a specific class resource
    console.log("\nReading ValueTree class documentation...");
    const valueTreeDocs = await client.readResource({ uri: "juce://class/ValueTree" });
    if (valueTreeDocs.contents && valueTreeDocs.contents[0] && valueTreeDocs.contents[0].text) {
      const text = valueTreeDocs.contents[0].text as string;
      console.log("ValueTree docs:", text.substring(0, 200) + "...");
    }
    
    // Test searching for classes
    console.log("\nSearching for 'Audio' classes...");
    const searchResult = await client.callTool({
      name: "search-classes",
      arguments: {
        query: "Audio"
      }
    });
    
    // Type assertion for content
    const content = searchResult.content as Array<{type: string, text: string}>;
    if (content && content.length > 0) {
      console.log("Search results:", content[0].text.substring(0, 200) + "...");
    }
    
    // Test getting class documentation
    console.log("\nGetting AudioBuffer class documentation...");
    const audioDocs = await client.callTool({
      name: "get-class-docs",
      arguments: {
        className: "AudioBuffer"
      }
    });
    
    // Type assertion for content
    const audioContent = audioDocs.content as Array<{type: string, text: string}>;
    if (audioContent && audioContent.length > 0) {
      console.log("AudioBuffer docs:", audioContent[0].text.substring(0, 200) + "...");
    }
    
    console.log("\nAll tests completed successfully!");
    
    // Clean up
    serverProcess.kill();
    process.exit(0);
  } catch (error) {
    console.error("Error in test client:", error);
    process.exit(1);
  }
}

main(); 
```

--------------------------------------------------------------------------------
/juce-docs-mcp-server/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import {
  fetchClassDocumentation,
  fetchClassList,
  searchClasses,
  formatClassDocumentation
} from "./juce-docs.js";

// Create an MCP server
const server = new McpServer({
  name: "JUCE Documentation Server",
  version: "1.0.0"
});

// Resource for getting documentation for a specific class
server.resource(
  "class-docs",
  new ResourceTemplate("juce://class/{className}", { list: undefined }),
  async (uri, { className }) => {
    console.log(`Fetching documentation for class: ${className}`);
    
    // Ensure className is a string
    const classNameStr = Array.isArray(className) ? className[0] : className;
    const doc = await fetchClassDocumentation(classNameStr);
    
    if (!doc) {
      return {
        contents: [{
          uri: uri.href,
          text: `Documentation for class '${classNameStr}' not found.`
        }]
      };
    }
    
    const markdown = formatClassDocumentation(doc);
    
    return {
      contents: [{
        uri: uri.href,
        text: markdown
      }]
    };
  }
);

// Resource for listing all available classes
server.resource(
  "class-list",
  "juce://classes",
  async (uri) => {
    console.log("Fetching list of all JUCE classes");
    
    const classes = await fetchClassList();
    
    return {
      contents: [{
        uri: uri.href,
        text: `# JUCE Classes\n\n${classes.map(c => `- [${c}](juce://class/${c})`).join('\n')}`
      }]
    };
  }
);

// Tool for searching classes
server.tool(
  "search-juce-classes",
  { query: z.string() },
  async ({ query }) => {
    console.log(`Searching for classes matching: ${query}`);
    
    const results = await searchClasses(query);
    
    if (results.length === 0) {
      return {
        content: [{ 
          type: "text", 
          text: `No classes found matching '${query}'.` 
        }]
      };
    }
    
    const markdown = `# Search Results for '${query}'\n\n${results.map(c => `- [${c}](juce://class/${c})`).join('\n')}`;
    
    return {
      content: [{ type: "text", text: markdown }]
    };
  }
);

// Tool for getting class documentation
server.tool(
  "get-juce-class-docs",
  { className: z.string() },
  async ({ className }) => {
    console.log(`Fetching documentation for class: ${className}`);
    
    const doc = await fetchClassDocumentation(className);
    
    if (!doc) {
      return {
        content: [{ 
          type: "text", 
          text: `Documentation for class '${className}' not found.` 
        }]
      };
    }
    
    const markdown = formatClassDocumentation(doc);
    
    return {
      content: [{ type: "text", text: markdown }]
    };
  }
);

// Prompt for exploring JUCE documentation
server.prompt(
  "explore-juce",
  { topic: z.string().optional() },
  ({ topic }) => ({
    messages: [{
      role: "user",
      content: {
        type: "text",
        text: topic 
          ? `Please help me understand the JUCE ${topic} functionality. What classes should I look at?` 
          : "Please help me explore the JUCE framework. What are the main components and classes I should know about?"
      }
    }]
  })
);

// Start the server
async function main() {
  try {
    console.log("Starting JUCE Documentation MCP Server...");
    
    const transport = new StdioServerTransport();
    await server.connect(transport);
    
    console.log("Server connected and ready to receive requests.");
  } catch (error) {
    console.error("Error starting server:", error);
    process.exit(1);
  }
}

main(); 
```

--------------------------------------------------------------------------------
/juce-docs-mcp-server/src/juce-docs.ts:
--------------------------------------------------------------------------------

```typescript
import fetch from 'node-fetch';
import * as cheerio from 'cheerio';

const BASE_URL = 'https://ccrma.stanford.edu/~jos/juce_modules';
// const BASE_URL = 'https://docs.juce.com/develop';
// const BASE_URL = 'https://docs.juce.com/master';
const INDEX_URL = `${BASE_URL}/annotated.html`;
const CLASS_URL_PATTERN = `${BASE_URL}/class{className}.html`;

/**
 * Fetches the HTML content from a URL
 */
async function fetchHtml(url: string): Promise<string> {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
    }
    return await response.text();
  } catch (error) {
    console.error(`Error fetching ${url}:`, error);
    throw error;
  }
}

/**
 * Fetches the list of all JUCE classes from the index page
 */
export async function fetchClassList(): Promise<string[]> {
  try {
    const html = await fetchHtml(INDEX_URL);
    const $ = cheerio.load(html);
    
    // Extract class names from the class list page
    const classes: string[] = [];
    
    // Look for links in the class list
    $('.directory tr.even, .directory tr.odd').each((_, element) => {
      const link = $(element).find('td.entry a');
      const href = link.attr('href');
      if (href && href.startsWith('class') && href.endsWith('.html')) {
        // Extract class name from href (e.g., "classValueTree.html" -> "ValueTree")
        const className = href.replace(/^class/, '').replace(/\.html$/, '');
        classes.push(className);
      }
    });
    
    return classes;
  } catch (error) {
    console.error('Error fetching class list:', error);
    throw error;
  }
}

/**
 * Represents the structure of JUCE class documentation
 */
export interface ClassDocumentation {
  className: string;
  description: string;
  methods: MethodDocumentation[];
  properties: PropertyDocumentation[];
  inheritance?: string;
  url: string;
}

export interface MethodDocumentation {
  name: string;
  signature: string;
  description: string;
}

export interface PropertyDocumentation {
  name: string;
  type: string;
  description: string;
}

/**
 * Fetches and parses documentation for a specific JUCE class
 */
export async function fetchClassDocumentation(className: string): Promise<ClassDocumentation | null> {
  try {
    const url = CLASS_URL_PATTERN.replace('{className}', className);
    const html = await fetchHtml(url);
    const $ = cheerio.load(html);
    
    // Extract class description
    const description = $('.contents .textblock').first().text().trim();
    
    // Extract methods
    const methods: MethodDocumentation[] = [];
    $('.memitem').each((_, element) => {
      const nameElement = $(element).find('.memname');
      if (nameElement.length) {
        const name = nameElement.text().trim().split('(')[0].trim();
        const signature = nameElement.parent().text().trim();
        const description = $(element).find('.memdoc').text().trim();
        
        methods.push({
          name,
          signature,
          description
        });
      }
    });
    
    // Extract properties (this is simplified and might need adjustment)
    const properties: PropertyDocumentation[] = [];
    $('.fieldtable tr').each((_, element) => {
      const nameElement = $(element).find('.fieldname');
      if (nameElement.length) {
        const name = nameElement.text().trim();
        const type = $(element).find('.fieldtype').text().trim();
        const description = $(element).find('.fielddoc').text().trim();
        
        properties.push({
          name,
          type,
          description
        });
      }
    });
    
    // Extract inheritance information
    let inheritance: string | undefined;
    $('.inheritance').each((_, element) => {
      inheritance = $(element).text().trim();
    });
    
    return {
      className,
      description,
      methods,
      properties,
      inheritance,
      url
    };
  } catch (error) {
    console.error(`Error fetching documentation for ${className}:`, error);
    return null;
  }
}

/**
 * Searches for classes matching a query string
 */
export async function searchClasses(query: string): Promise<string[]> {
  try {
    const allClasses = await fetchClassList();
    const lowerQuery = query.toLowerCase();
    
    return allClasses.filter(className => 
      className.toLowerCase().includes(lowerQuery)
    );
  } catch (error) {
    console.error('Error searching classes:', error);
    throw error;
  }
}

/**
 * Formats class documentation as markdown
 */
export function formatClassDocumentation(doc: ClassDocumentation): string {
  let markdown = `# ${doc.className}\n\n`;
  
  if (doc.inheritance) {
    markdown += `**Inheritance:** ${doc.inheritance}\n\n`;
  }
  
  markdown += `${doc.description}\n\n`;
  markdown += `[View Online Documentation](${doc.url})\n\n`;
  
  if (doc.methods.length > 0) {
    markdown += `## Methods\n\n`;
    doc.methods.forEach(method => {
      markdown += `### ${method.name}\n\n`;
      markdown += `\`\`\`cpp\n${method.signature}\n\`\`\`\n\n`;
      markdown += `${method.description}\n\n`;
    });
  }
  
  if (doc.properties.length > 0) {
    markdown += `## Properties\n\n`;
    doc.properties.forEach(prop => {
      markdown += `### ${prop.name}\n\n`;
      markdown += `**Type:** ${prop.type}\n\n`;
      markdown += `${prop.description}\n\n`;
    });
  }
  
  return markdown;
} 

```