DEVELOPERS.org•23 kB
#+TITLE: Developer Guide for MCP QR Code Server
* Prerequisites
** System Requirements
- Node.js >= 18.0.0
- ~qrencode~ utility installed on your system
*** MacOS Installation
#+begin_src bash
brew install qrencode
#+end_src
*** Linux (Debian/Ubuntu) Installation
#+begin_src bash
apt-get install qrencode
#+end_src
*** Linux (Red Hat/Fedora) Installation
#+begin_src bash
dnf install qrencode
#+end_src
* Installation
#+begin_src bash
# Clone the repository
git clone https://github.com/jwalsh/mcp-server-qrcode.git
cd mcp-server-qrcode
# Install dependencies
npm install
# Build the server
npm run build
#+end_src
* Development Setup
** Initial Project Setup
#+begin_src bash
# Initialize the project
make initialize
# Set up development environment
make setup
#+end_src
** Running the Project
#+begin_src bash
# Start in development mode
make dev
# Quick start the application
make quickstart
#+end_src
* Running the Server
#+begin_src bash
# Start the MCP server
npm start
# For local development with CLI features
npm run cli -- -g "Hello World"
# Test piping with the CLI
echo "Hello World" | npm run cli
#+end_src
The server will start and listen for MCP requests over stdin/stdout.
** Deployment Modes
This project supports two modes:
1. *MCP Server Mode (Production)*: When installed globally with ~npm install -g~, the ~mcp-server-qrcode~ command starts the MCP server with no CLI functionality. This is what end users will use with Claude Desktop, MCP Inspector, etc.
2. *CLI Mode (Development)*: For local development and testing, use ~npm run cli~ to access CLI features including stdin piping and command-line arguments. These features are not included in the published package.
* Architecture
The project consists of two main components:
1. A command-line interface (CLI) for generating QR codes (for development/testing)
2. An MCP server implementation for integration with MCP clients (primary use case)
Both components share the core QR code generation functionality defined in ~src/qrcode.ts~, which provides a unified interface for different output formats.
** Command-Line Interface (CLI)
The CLI supports multiple modes of operation (primarily for development and testing):
1. *Pipe mode*: Accept input via stdin
#+begin_src bash
echo "https://example.com" | npm run cli
#+end_src
2. *Generate mode*: Generate QR code with command line options
#+begin_src bash
npm run cli -- -g "https://example.com" -s 300 -e H
#+end_src
** MCP Server Implementation
The MCP server enables interaction with MCP clients like Claude Desktop and Inspector. It's implemented following the Model Context Protocol standards.
*** Core Implementation Structure
The ~index.ts~ file contains the primary MCP server implementation:
#+begin_src typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
// Create an MCP server
const server = new McpServer({
name: "QR Code Generator",
version: "0.1.0"
});
// Add the QR code generation tool
server.tool(
"generate-qrcode",
{
content: z.string().describe("The text content to encode in the QR code"),
errorCorrectionLevel: z.enum(["L", "M", "Q", "H"])
.describe("Error correction level")
.optional()
.default("M"),
// Other parameters...
},
async ({ content, errorCorrectionLevel, size, format }) => {
// Tool implementation...
}
);
export default server;
#+end_src
*** Server Entry Point
The ~main.ts~ file provides the entry point that connects the server to a transport:
#+begin_src typescript
import server from './index.js';
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// Create a stdio transport
const transport = new StdioServerTransport();
// Connect the server to the transport
server.connect(transport)
.catch(error => {
console.error('Failed to start server:', error);
process.exit(1);
});
#+end_src
*** Example MCP Server Implementations
For more sophisticated MCP server examples and patterns, refer to these implementations:
1. *File System Server*: [[https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/index.ts][filesystem/index.ts]]
- Provides access to local files
- Example of handling file resources
2. *GitHub Server*: [[https://github.com/modelcontextprotocol/servers/blob/main/src/github/index.ts][github/index.ts]]
- Provides access to GitHub repositories
- Example of API integration
3. *Google Maps Server*: [[https://github.com/modelcontextprotocol/servers/blob/main/src/google-maps/index.ts][google-maps/index.ts]]
- Provides access to Google Maps
- Example of API key authentication
4. *Everything Server*: [[https://github.com/modelcontextprotocol/servers/blob/main/src/everything/index.ts][everything/index.ts]]
- Desktop search integration
- Example of local application integration
* Using with MCP Clients
** Claude Desktop
#+begin_src json
{
"mcpServers": {
"qrcode": {
"args": [
"$HOME/projects/mcp-server-qrcode/build/cli.js"
],
"command": "node"
}
}
}
#+end_src
** MCP Inspector
This server can be used with any MCP-compatible client. Here's how to use it with the MCP Inspector:
#+begin_src bash
# For MCP Inspector, use the main.js file
mcp-inspector -- node build/main.js
# Or use the Makefile target
make inspector-dev
#+end_src
For the MCP Inspector UI, use these settings:
- Transport Type: STDIO
- Command: node
- Arguments: build/main.js
Important: Always use build/main.js as the entry point for the MCP server when using the inspector.
** Command Line Piping
The server also supports direct piping from the command line:
#+begin_src bash
echo "https://example.com" | mcp-server-qrcode
cat myfile.txt | mcp-server-qrcode
#+end_src
* Debugging
- https://modelcontextprotocol.io/docs/tools/debugging
* Testing
#+begin_src bash
# Run tests
make test
# Run tests with watch
make test-watch
# Run tests with coverage
make test-coverage
#+end_src
* JSONRPC Method Comparison
The following table shows the JSONRPC methods supported across different MCP implementations:
| JSONRPC Method | MCP Inspector | mcp.el | QR Code Server |
|-----------------------------+---------------+--------------------------------------------------------+----------------|
| ~initialize~ | ✅ | ✅ Via ~mcp-async-initialize-message~ | ✅ |
| ~ping~ | ✅ | ✅ Via ~mcp-async-ping~ | ✅ |
| ~notifications/initialized~ | ✅ | ✅ Via ~mcp-notify~ | ✅ |
| ~notifications/stderr~ | ✅ | ❌ | ❌ |
| ~tools/list~ | ✅ | ✅ Via ~mcp-async-list-tools~ | ✅ |
| ~tools/call~ | ✅ | ✅ Via ~mcp-call-tool~ & ~mcp-async-call-tool~ | ✅ |
| ~prompts/list~ | ✅ | ✅ Via ~mcp-async-list-prompts~ | ✅ |
| ~prompts/get~ | ✅ | ✅ Via ~mcp-get-prompt~ & ~mcp-async-get-prompt~ | ✅ |
| ~resources/list~ | ✅ | ✅ Via ~mcp-async-list-resources~ | ✅ |
| ~resources/read~ | ✅ | ✅ Via ~mcp-read-resource~ & ~mcp-async-read-resource~ | ✅ |
| ~resources/templates/list~ | ✅ | ✅ Via ~mcp-async-list-resource-templates~ | ✅ |
* Interactive Tests
:PROPERTIES:
:CUSTOM_ID: tests
:END:
These tests can be run interactively to verify the functionality of the MCP QR Code Server.
** Build Project First
:PROPERTIES:
:CUSTOM_ID: build
:END:
#+begin_src bash :tangle scripts/build.sh :mkdirp yes :shebang "#!/bin/bash"
# Ensure the project is built before running tests
npm run build
echo "✅ Project built successfully"
#+end_src
#+RESULTS:
: ✅ Project built successfully
** Core Method Tests
:PROPERTIES:
:CUSTOM_ID: core-methods
:END:
*** Initialize Method
#+begin_src bash :tangle scripts/test-initialize.sh :mkdirp yes :results output :exports both :results output :exports both :shebang "#!/bin/bash"
echo '{"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"roots":{"listChanged":true}},"clientInfo":{"name":"test-client","version":"0.1.0"}},"id":1,"jsonrpc":"2.0"}' | node build/main.js | jq '.'
#+end_src
#+RESULTS:
#+begin_example
{
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {}
},
"serverInfo": {
"name": "QR Code Generator",
"version": "0.1.0"
}
},
"jsonrpc": "2.0",
"id": 1
}
#+end_example
*** Ping Method
#+begin_src bash :tangle scripts/test-ping.sh :mkdirp yes :results output :exports both :shebang "#!/bin/bash"
echo '{"method":"ping","params":{},"id":1,"jsonrpc":"2.0"}' | node build/main.js | jq '.'
#+end_src
#+RESULTS:
#+begin_example
{
"result": "pong",
"jsonrpc": "2.0",
"id": 1
}
#+end_example
** Tools Interface Tests
:PROPERTIES:
:CUSTOM_ID: tools-tests
:END:
*** List Tools
#+begin_src bash :tangle scripts/test-tools-list.sh :mkdirp yes :results output :exports both :shebang "#!/bin/bash"
echo '{"method":"tools/list","params":{},"id":1,"jsonrpc":"2.0"}' | node build/main.js | jq '.result.tools[] | {name, description}'
#+end_src
#+RESULTS:
#+begin_example
{
"name": "generate-qrcode",
"description": "Generate a QR code from text content"
}
#+end_example
*** Call QR Code Generation Tool (Text Format)
#+begin_src bash :tangle scripts/test-tools-call-text.sh :mkdirp yes :results output :exports both :shebang "#!/bin/bash"
echo '{"method":"tools/call","params":{"name":"generate-qrcode","arguments":{"content":"https://anthropic.com","format":"text"}},"id":2,"jsonrpc":"2.0"}' | node build/main.js | jq '.result.content[] | select(.type=="text") | .text'
#+end_src
#+RESULTS:
#+begin_example
"QR code generated for: https://anthropic.com"
"██████████████████████████████████████████████\n██████████████████████████████████████████████\n████ ██████ ████\n████ ██████████ ██████ ██████████ ████\n████ ██ ██ ██████ ██ ██ ████\n████ ██ ██ ██████ ██ ██ ████\n████ ██ ██ ██████ ██ ██ ████\n████ ██████████ ██████ ██████████ ████\n████ ██████ ████\n████████████ ██ ██ ██ ██ ██ ██████\n██████████ ██ ██ ██████ ██████\n██████████ ████ ████ ██████\n██████████ ██ ██ ██ ██ ████ ████\n██████████ ██████ ██ ██ ██ ██████\n████ ██ ██████ ██████\n████ ██████████ ████ ████ ██ ██████\n████ ██ ██ ██ ██████ ████████\n████ ██ ██ ██████ ██ ██ ██████\n████ ██ ██ ██ ██ ██ ██ ████████\n████ ██████████ ██ ██ ██ ██████\n████ ██ ██ ██ ██████████\n██████████████████████████████████████████████\n██████████████████████████████████████████████"
#+end_example
*** Call QR Code Generation Tool (Image Format)
#+begin_src bash :tangle scripts/test-tools-call-image.sh :mkdirp yes :results output :exports both :shebang "#!/bin/bash"
# This will generate a base64-encoded image - show just the first part
echo '{"method":"tools/call","params":{"name":"generate-qrcode","arguments":{"content":"https://anthropic.com","format":"image"}},"id":2,"jsonrpc":"2.0"}' | node build/main.js | jq -r '.result.content[] | select(.type=="image") | .data' | head -c 50
echo "... [base64 data truncated]"
#+end_src
#+RESULTS:
: iVBORw0KGgoAAAANSUhEUgAAAhwAAAIcAQMAAACSSQR3AAA... [base64 data truncated]
** Prompts Interface Tests
:PROPERTIES:
:CUSTOM_ID: prompts-tests
:END:
*** List Prompts
#+begin_src bash :tangle scripts/test-prompts-list.sh :mkdirp yes :results output :exports both :shebang "#!/bin/bash"
echo '{"method":"prompts/list","params":{},"id":1,"jsonrpc":"2.0"}' | node build/main.js | jq '.result.prompts'
#+end_src
#+RESULTS:
#+begin_example
[
{
"name": "qrcode-formatter",
"description": "Format QR code input for various use cases",
"inputSchema": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"url",
"wifi",
"contact",
"text"
],
"description": "Type of QR code content to format"
},
"parameters": {
"type": "object",
"description": "Parameters specific to the QR code type"
}
},
"required": [
"type",
"parameters"
]
}
}
]
#+end_example
*** Get Prompt (Wi-Fi)
#+begin_src bash :tangle scripts/test-prompts-get-wifi.sh :mkdirp yes :results output :exports both :shebang "#!/bin/bash"
echo '{"method":"prompts/get","params":{"name":"qrcode-formatter","arguments":{"type":"wifi","parameters":{"ssid":"GuestWiFi","password":"Welcome123","encryption":"WPA"}}},"id":2,"jsonrpc":"2.0"}' | node build/main.js | jq '.result.content'
#+end_src
#+RESULTS:
: "WIFI:S:GuestWiFi;T:WPA;P:Welcome123;;"
** Resources Interface Tests
:PROPERTIES:
:CUSTOM_ID: resources-tests
:END:
*** List Resources
#+begin_src bash :tangle scripts/test-resources-list.sh :mkdirp yes :results output :exports both :shebang "#!/bin/bash"
echo '{"method":"resources/list","params":{},"id":1,"jsonrpc":"2.0"}' | node build/main.js | jq '.result.resources'
#+end_src
#+RESULTS:
#+begin_example
[
{
"uri": "qrcode-examples",
"description": "Examples of different QR code formats",
"mimeType": "text/markdown"
},
{
"uri": "qrcode-specs",
"description": "Technical specifications for QR code generation",
"mimeType": "text/markdown"
}
]
#+end_example
*** Read Resource
#+begin_src bash :tangle scripts/test-resources-read.sh :mkdirp yes :results output :exports both :shebang "#!/bin/bash"
echo '{"method":"resources/read","params":{"uri":"qrcode-examples"},"id":2,"jsonrpc":"2.0"}' | node build/main.js | jq '.result.content'
#+end_src
#+RESULTS:
: "# QR Code Examples\n\n## URL\n`https://example.com`\n\n## Wi-Fi\n`WIFI:S:MyNetwork;T:WPA;P:MyPassword;;`\n\n## Contact\n```\nBEGIN:VCARD\nN:Doe;John;;;\nFN:John Doe\nEMAIL:john@example.com\nTEL:555-123-4567\nEND:VCARD\n```"
*** List Resource Templates
#+begin_src bash :tangle scripts/test-resources-templates-list.sh :mkdirp yes :results output :exports both :shebang "#!/bin/bash"
echo '{"method":"resources/templates/list","params":{},"id":3,"jsonrpc":"2.0"}' | node build/main.js | jq '.result.resourceTemplates'
#+end_src
#+RESULTS:
#+begin_example
[
{
"name": "wifi-template",
"description": "Template for Wi-Fi QR codes",
"inputSchema": {
"type": "object",
"properties": {
"ssid": {
"type": "string",
"description": "Network name"
},
"encryption": {
"type": "string",
"enum": [
"WPA",
"WEP",
"nopass"
],
"description": "Encryption type"
},
"password": {
"type": "string",
"description": "Network password"
},
"hidden": {
"type": "boolean",
"description": "Whether the network is hidden",
"default": false
}
},
"required": [
"ssid",
"encryption"
]
}
}
]
#+end_example
* JSON Schema Reference
:PROPERTIES:
:CUSTOM_ID: json-schemas
:END:
** Core Methods
*** ~initialize~ Request/Response Schema
#+begin_src js :tangle schemas/initialize.js :mkdirp yes :results output :exports both
// initialize request
{
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {
"roots": {
"listChanged": true
}
},
"clientInfo": {
"name": "mcp-client-name",
"version": "0.1.0"
}
},
"id": 1,
"jsonrpc": "2.0"
}
// initialize response
{
"result": {
"protocolVersion": "2024-11-05",
"serverInfo": {
"name": "mcp-server-qrcode",
"version": "0.1.0"
},
"capabilities": {
"tools": {},
"prompts": {},
"resources": {}
}
},
"id": 1,
"jsonrpc": "2.0"
}
#+end_src
*** ~tools/call~ Request/Response Schema
#+begin_src js :tangle schemas/tools-call.js :mkdirp yes :results output :exports both
// tools/call request for QR code generation
{
"method": "tools/call",
"params": {
"name": "generate-qrcode",
"arguments": {
"content": "https://example.com",
"format": "image",
"errorCorrectionLevel": "M",
"size": 3
}
},
"id": 2,
"jsonrpc": "2.0"
}
// tools/call response
{
"result": {
"content": [
{
"type": "text",
"text": "QR code generated for: https://example.com"
},
{
"type": "image",
"mimeType": "image/png",
"data": "base64-encoded-image-data"
}
]
},
"id": 2,
"jsonrpc": "2.0"
}
#+end_src
*** ~prompts/get~ Request/Response Schema
#+begin_src js :tangle schemas/prompts-get.js :mkdirp yes :results output :exports both
// prompts/get request for Wi-Fi
{
"method": "prompts/get",
"params": {
"name": "qrcode-formatter",
"arguments": {
"type": "wifi",
"parameters": {
"ssid": "MyWiFi",
"password": "MyPassword",
"encryption": "WPA"
}
}
},
"id": 2,
"jsonrpc": "2.0"
}
// prompts/get response
{
"result": {
"content": "WIFI:S:MyWiFi;T:WPA;P:MyPassword;;"
},
"id": 2,
"jsonrpc": "2.0"
}
#+end_src
* References
** MCP Protocol Documentation
- [[https://modelcontextprotocol.io/docs/specification/jsonrpc][MCP JSONRPC Specification]]
- [[https://modelcontextprotocol.io/docs/tools/mcp-inspector][MCP Inspector Usage]]
- [[https://modelcontextprotocol.io/docs/tools/debugging][MCP Debugging Guide]]
** Example MCP Server Implementations
- [[https://github.com/modelcontextprotocol/servers/blob/main/src/filesystem/index.ts][File System Server]]
- [[https://github.com/modelcontextprotocol/servers/blob/main/src/github/index.ts][GitHub Server]]
- [[https://github.com/modelcontextprotocol/servers/blob/main/src/google-maps/index.ts][Google Maps Server]]
* Linting and Formatting
#+begin_src bash
# Lint code
make lint
# Automatically fix linting issues
make lint-fix
# Format code
make format
#+end_src
* Continuous Integration
#+begin_src bash
# Run all checks (lint, format, typecheck, test)
make ci
#+end_src
* Release Process
** Release Workflow
Follow these steps to release a new version of the package:
*** Automated Release Process
For the easiest release experience, use the provided release script:
#+begin_src bash
# For patch releases (default)
./scripts/release.sh
# For minor releases
./scripts/release.sh minor
# For major releases
./scripts/release.sh major
#+end_src
The script handles most of the process but pauses for manual npm publish (due to 2FA) and final verification.
*** Manual Release Process
If you need to execute the release steps manually:
1. *Update Version*: Bump the version in package.json
#+begin_src bash
# Use npm version to update package.json (creates a commit and tag automatically)
npm version patch -m "chore: bump version to %s" # or minor, major
#+end_src
2. *Generate Changelog*: Update the CHANGELOG.org file
#+begin_src bash
npm run changelog
#+end_src
3. *Commit Changelog*: Commit the changelog updates with [skip ci]
#+begin_src bash
# CRITICAL: Never use GPG signing for commits in this repo
git add CHANGELOG.org
git commit --no-gpg-sign -m "docs: update CHANGELOG.org for new version [skip ci]"
#+end_src
4. *Push Changes*: Push the commits and tags
#+begin_src bash
git push origin main
git push origin --tags
#+end_src
5. *Create GitHub Release*: Create a release with notes from the changelog
#+begin_src bash
# Generate release tarball
npm pack
# Create draft release with the tarball
VERSION=$(jq -r .version package.json)
gh release create "v$VERSION" *.tgz --title "v$VERSION" --notes "See CHANGELOG.org for details" --draft
#+end_src
6. *Pre-Release Verification*: Test with MCP Inspector before publishing
#+begin_src bash
# Verify server functionality with MCP Inspector
make inspector-dev
# Test in the inspector UI by creating a QR code
#+end_src
7. *Publish to npm*: Publish the package (requires 2FA)
#+begin_src bash
npm publish
#+end_src
8. *Verify Publication*: Confirm the package is properly published
#+begin_src bash
# Verify npm package
npm view @jwalsh/mcp-server-qrcode version
#+end_src
9. *Publish GitHub Release*: Remove draft status from the release
#+begin_src bash
gh release edit "v$VERSION" --draft=false
#+end_src
When validating with MCP Inspector, the successful connection should look like this:
[[file:static/localhost_5173_.png][MCP Inspector Validation]]
* Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Run ~make ci~ to ensure all checks pass
5. Submit a pull request
* Troubleshooting
- Ensure you're using Node.js 18.0.0 or higher
- Install ~qrencode~ utility for your system
- Run ~make initialize~ if you encounter dependency issues
- Check ~make setup~ for environment verification