#
tokens: 3684/50000 7/7 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | node_modules/
2 | build/
3 | *.log
4 | .env*
```

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

```markdown
 1 | # Handwriting OCR MCP Server
 2 | [![smithery badge](https://smithery.ai/badge/@Handwriting-OCR/handwriting-ocr-mcp-server)](https://smithery.ai/server/@Handwriting-OCR/handwriting-ocr-mcp-server)
 3 | 
 4 | A Model Context Protocol (MCP) Server for [Handwriting OCR](https://www.handwritingocr.com) API.
 5 | 
 6 | ## Overview
 7 | 
 8 | The Handwriting OCR MCP Server enables integration between MCP clients and the Handwriting OCR service. This document outlines the setup process and provides a basic example of using the client.
 9 | 
10 | This server allows you to upload images and PDF documents, check their status, and retrieve the OCR result as Markdown.
11 | 
12 | ## Tools
13 | 
14 | ### Transcription
15 | 
16 | *   Upload Document
17 | *   Check Status
18 | *   Get Text
19 | 
20 | ## Prerequisites
21 | 
22 | Before you begin, ensure you have the following:
23 | 
24 | *   Node.js installed on your system (recommended version 18.x or higher).
25 | *   An active account on the [Handwriting OCR Platform](https://www.handwritingocr.com) and an active [API token](https://www.handwritingocr.com/settings/api).
26 | 
27 | ## Installation
28 | 
29 | ### Installing via Smithery
30 | 
31 | To install handwriting-ocr-mcp-server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@Handwriting-OCR/handwriting-ocr-mcp-server):
32 | 
33 | ```bash
34 | npx -y @smithery/cli install @Handwriting-OCR/handwriting-ocr-mcp-server --client claude
35 | ```
36 | 
37 | ### Installing manually for Claude Desktop
38 | 
39 | To use the Handwriting OCR MCP Server in Claude Desktop application, use:
40 | 
41 | ```json
42 | {
43 |     "mcpServers": {
44 |         "handwriting-ocr": {
45 |             "command": "node",
46 |             "args": [
47 |                 "/Users/mateo/Local/Code/MCP/handwriting-ocr/build/index.js"
48 |             ],
49 |             "env": {
50 |                 "API_TOKEN": "your-api-token",
51 |             },
52 |             "disabled": false,
53 |             "autoApprove": []
54 |         }
55 |     }
56 | }
57 | ```
58 | 
59 | ## Configuration
60 | 
61 | The Handwriting OCR MCP Server supports environment variables to be set for authentication and configuration:
62 | 
63 | *   `API_TOKEN`: Your API token.
64 | 
65 | You can find these values in the API settings dashboard on the [Handwriting OCR Platform](https://www.handwritingocr.com).
66 | 
67 | ## Support
68 | 
69 | Please refer to the [Handwriting OCR API Documentation](https://www.handwritingocr.com/api/docs).
70 | 
71 | For support with the Handwriting OCR MCP Server, please submit a [GitHub Issue](https://github.com/modelcontextprotocol/servers/issues).
72 | 
73 | ## About
74 | 
75 | Model Context Protocol (MCP) Server for Handwriting OCR Platform
76 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | FROM node:lts-alpine
 3 | 
 4 | WORKDIR /app
 5 | 
 6 | # Copy package files
 7 | COPY package.json package-lock.json ./
 8 | 
 9 | # Install dependencies (ignore scripts to avoid any unwanted behavior)
10 | RUN npm install --ignore-scripts
11 | 
12 | # Copy the rest of the source code
13 | COPY . .
14 | 
15 | # Build the project
16 | RUN npm run build
17 | 
18 | # Expose port if needed (not specified, but MCP runs on stdio)
19 | 
20 | # Run the MCP server
21 | CMD [ "node", "build/index.js" ]
22 | 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - apiToken
10 |     properties:
11 |       apiToken:
12 |         type: string
13 |         description: Your Handwriting OCR API token
14 |   commandFunction:
15 |     # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
16 |     |-
17 |     (config) => ({
18 |       command: 'node',
19 |       args: ['build/index.js'],
20 |       env: { API_TOKEN: config.apiToken }
21 |     })
22 |   exampleConfig:
23 |     apiToken: your-api-token
24 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "handwriting-ocr",
 3 |   "version": "0.1.0",
 4 |   "description": "A Model Context Protocol server to enable integration between MCP clients and the Handwriting OCR service.",
 5 |   "private": true,
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "handwriting-ocr": "./build/index.js"
 9 |   },
10 |   "files": [
11 |     "build"
12 |   ],
13 |   "scripts": {
14 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
15 |     "prepare": "npm run build",
16 |     "watch": "tsc --watch",
17 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
18 |   },
19 |   "dependencies": {
20 |     "@modelcontextprotocol/sdk": "0.6.0",
21 |     "axios": "^1.8.2"
22 |   },
23 |   "devDependencies": {
24 |     "@types/node": "^20.11.24",
25 |     "typescript": "^5.3.3"
26 |   }
27 | }
28 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ListToolsRequestSchema,
  7 | } from '@modelcontextprotocol/sdk/types.js';
  8 | import axios from 'axios';
  9 | import * as fs from 'fs';
 10 | import FormData from 'form-data';
 11 | 
 12 | const API_TOKEN = process.env.API_TOKEN;
 13 | 
 14 | const server = new Server(
 15 |   {
 16 |     name: 'handwriting-ocr',
 17 |     version: '0.1.0',
 18 |   },
 19 |   {
 20 |     capabilities: {
 21 |       tools: {},
 22 |     },
 23 |   }
 24 | );
 25 | 
 26 | server.onerror = (error) => console.error('[MCP Error]', error);
 27 | process.on('SIGINT', async () => {
 28 |   await server.close();
 29 |   process.exit(0);
 30 | });
 31 | 
 32 | server.setRequestHandler(ListToolsRequestSchema, async () => ({
 33 |   tools: [
 34 |     {
 35 |       name: 'upload_document',
 36 |       description: 'Upload a document to Handwriting OCR API for transcription',
 37 |       inputSchema: {
 38 |         type: 'object',
 39 |         properties: {
 40 |           file: {
 41 |             type: 'string',
 42 |             description: 'Path to the document (PDF, JPG, PNG, etc.)',
 43 |           },
 44 |           delete_after: {
 45 |             type: 'integer',
 46 |             description: 'Seconds until auto-deletion (optional)',
 47 |           },
 48 |           extractor_id: {
 49 |             type: 'string',
 50 |             description: 'Extractor ID (required if action is extractor, will be ignored)',
 51 |           },
 52 |           prompt_id: {
 53 |             type: 'string',
 54 |             description: 'Prompt ID (requires Enterprise subscription, will be ignored)',
 55 |           },
 56 |         },
 57 |         required: ['file'],
 58 |       },
 59 |     },
 60 |     {
 61 |       name: 'check_status',
 62 |       description: 'Check the status of a document',
 63 |       inputSchema: {
 64 |         type: 'object',
 65 |         properties: {
 66 |           id: {
 67 |             type: 'string',
 68 |             description: 'Document ID',
 69 |           },
 70 |         },
 71 |         required: ['id'],
 72 |       },
 73 |     },
 74 |     {
 75 |       name: 'get_text',
 76 |       description: 'Retrieve the transcribed text from a document',
 77 |       inputSchema: {
 78 |         type: 'object',
 79 |         properties: {
 80 |           id: {
 81 |             type: 'string',
 82 |             description: 'Document ID',
 83 |           },
 84 |         },
 85 |         required: ['id'],
 86 |       },
 87 |     },
 88 |   ],
 89 | }));
 90 | 
 91 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
 92 |   if (!API_TOKEN) {
 93 |     throw new Error('API_TOKEN environment variable is required');
 94 |   }
 95 | 
 96 |   switch (request.params.name) {
 97 |     case 'upload_document': {
 98 |       interface FileObject {
 99 |         data: any;
100 |         name: string;
101 |       }
102 | 
103 |       const file = request.params.arguments?.file as string | FileObject;
104 |       if (!file) {
105 |         throw new Error('File is required');
106 |       }
107 | 
108 |       let fileData: Buffer;
109 |       let fileName: string;
110 | 
111 |       if (typeof file === 'string') {
112 |         // File path provided
113 |         const filePath = file;
114 |         fileData = fs.readFileSync(filePath);
115 |         fileName = filePath.split('/').pop() || 'document';
116 |       } else {
117 |         // File object (attachment data) provided
118 |         fileData = Buffer.from(file.data);
119 |         fileName = file.name;
120 |       }
121 | 
122 |       const formData = new FormData();
123 |       formData.append('file', fileData, fileName);
124 |       formData.append('action', 'transcribe');
125 | 
126 |       const deleteAfter = request.params.arguments?.delete_after;
127 |       if (deleteAfter) {
128 |         formData.append('delete_after', String(deleteAfter));
129 |       }
130 | 
131 |       try {
132 |         const response = await axios.post(
133 |           'https://www.handwritingocr.com/api/v3/documents',
134 |           formData,
135 |           {
136 |             headers: {
137 |               'Content-Type': `multipart/form-data; boundary=${formData.getBoundary()}`,
138 |               Authorization: `Bearer ${API_TOKEN}`,
139 |               Accept: 'application/json',
140 |             },
141 |           }
142 |         );
143 | 
144 |         return {
145 |           content: [
146 |             {
147 |               type: 'text',
148 |               text: JSON.stringify({
149 |                 id: response.data.id,
150 |                 status: response.data.status,
151 |               }),
152 |             },
153 |           ],
154 |         };
155 |       } catch (error: any) {
156 |         console.error('[API Error]', error);
157 |         throw new Error(`Handwriting OCR API error: ${error.message}`);
158 |       }
159 |     }
160 |     case 'check_status': {
161 |       const documentId = String(request.params.arguments?.id);
162 |       if (!documentId) {
163 |         throw new Error('Document ID is required');
164 |       }
165 | 
166 |       try {
167 |         const response = await axios.get(
168 |           `https://www.handwritingocr.com/api/v3/documents/${documentId}`,
169 |           {
170 |             headers: {
171 |               Authorization: `Bearer ${API_TOKEN}`,
172 |               Accept: 'application/json',
173 |             },
174 |           }
175 |         );
176 | 
177 |         return {
178 |           content: [
179 |             {
180 |               type: 'text',
181 |               text: JSON.stringify({
182 |                 id: response.data.id,
183 |                 file_name: response.data.file_name,
184 |                 action: response.data.action,
185 |                 page_count: response.data.page_count,
186 |                 status: response.data.status,
187 |                 created_at: response.data.created_at,
188 |                 updated_at: response.data.updated_at,
189 |               }),
190 |             },
191 |           ],
192 |         };
193 |       } catch (error: any) {
194 |         console.error('[API Error]', error);
195 |         throw new Error(`Handwriting OCR API error: ${error.message}`);
196 |       }
197 |     }
198 |     case 'get_text': {
199 |       const documentId = String(request.params.arguments?.id);
200 |       if (!documentId) {
201 |         throw new Error('Document ID is required');
202 |       }
203 | 
204 |       try {
205 |         const response = await axios.get(
206 |           `https://www.handwritingocr.com/api/v3/documents/${documentId}.txt`,
207 |           {
208 |             headers: {
209 |               Authorization: `Bearer ${API_TOKEN}`,
210 |               Accept: 'text/plain',
211 |             },
212 |             responseType: 'text',
213 |           }
214 |         );
215 | 
216 |         return {
217 |           content: [
218 |             {
219 |               type: 'text',
220 |               text: response.data,
221 |             },
222 |           ],
223 |         };
224 |       } catch (error: any) {
225 |         console.error('[API Error]', error);
226 |         throw new Error(`Handwriting OCR API error: ${error.message}`);
227 |       }
228 |     }
229 |     default:
230 |       throw new Error('Unknown tool');
231 |   }
232 | });
233 | 
234 | async function main() {
235 |   const transport = new StdioServerTransport();
236 |   await server.connect(transport);
237 |   console.error('Handwriting OCR MCP server running on stdio');
238 | }
239 | 
240 | main().catch(console.error);
241 | 
```