# 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 | [](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 |
```