mathpix-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # MCP (Model Context Protocol) integration for Mathpix
4
+ #
5
+ # This module provides MCP server functionality using the official Ruby MCP SDK.
6
+ # All 9 tools are thin delegates to the core Mathpix::Client.
7
+ #
8
+ # Usage:
9
+ # require 'mathpix/mcp'
10
+ #
11
+ # Mathpix.configure do |config|
12
+ # config.app_id = ENV['MATHPIX_APP_ID']
13
+ # config.app_key = ENV['MATHPIX_APP_KEY']
14
+ # end
15
+ #
16
+ # Mathpix::MCP::Server.run
17
+
18
+ # Load MCP components (stdio server only)
19
+ require_relative 'mcp/server'
20
+ require_relative 'mcp/base_tool'
21
+
22
+ # Load all 9 tools
23
+ require_relative 'mcp/tools/convert_image_tool'
24
+ require_relative 'mcp/tools/convert_document_tool'
25
+ require_relative 'mcp/tools/convert_strokes_tool'
26
+ require_relative 'mcp/tools/batch_convert_tool'
27
+ require_relative 'mcp/tools/check_document_status_tool'
28
+ require_relative 'mcp/tools/search_results_tool'
29
+ require_relative 'mcp/tools/get_usage_tool'
30
+ require_relative 'mcp/tools/get_account_info_tool'
31
+ require_relative 'mcp/tools/list_formats_tool'
@@ -0,0 +1,387 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mathpix
4
+ # OCR Result object
5
+ class Result
6
+ attr_reader :data, :source_path
7
+
8
+ def initialize(data, source_path = nil)
9
+ @data = data
10
+ @source_path = source_path
11
+ end
12
+
13
+ # Get source URL if image was processed from URL
14
+ #
15
+ # @return [String, nil] URL if source was remote, nil otherwise
16
+ def source_url
17
+ return nil unless @source_path.is_a?(String)
18
+
19
+ @source_path.start_with?('http://', 'https://') ? @source_path : nil
20
+ end
21
+
22
+ # Text result
23
+ # @return [String, nil]
24
+ def text
25
+ data['text']
26
+ end
27
+
28
+ # LaTeX styled output
29
+ # @return [String, nil]
30
+ def latex
31
+ data['latex_styled'] || data['latex']
32
+ end
33
+
34
+ alias latex_styled latex
35
+
36
+ # Simplified LaTeX
37
+ # @return [String, nil]
38
+ def latex_simplified
39
+ data['latex_simplified']
40
+ end
41
+
42
+ # MathML output
43
+ # @return [String, nil]
44
+ def mathml
45
+ data['mathml']
46
+ end
47
+
48
+ # AsciiMath output
49
+ # @return [String, nil]
50
+ def asciimath
51
+ data['asciimath']
52
+ end
53
+
54
+ # HTML output (may contain SVG)
55
+ # @return [String, nil]
56
+ def html
57
+ data['html']
58
+ end
59
+
60
+ # Confidence score
61
+ # @return [Float] 0.0-1.0
62
+ def confidence
63
+ data['confidence'] || 0.0
64
+ end
65
+
66
+ # Confidence rate (alternative)
67
+ # @return [Float] 0.0-1.0
68
+ def confidence_rate
69
+ data['confidence_rate'] || confidence
70
+ end
71
+
72
+ # Created timestamp
73
+ # @return [Time, nil]
74
+ def created_at
75
+ Time.parse(data['created']) if data['created']
76
+ rescue ArgumentError
77
+ nil
78
+ end
79
+
80
+ # Request ID
81
+ # @return [String, nil]
82
+ def request_id
83
+ data['request_id']
84
+ end
85
+
86
+ # Processing time in milliseconds
87
+ # @return [Integer, nil]
88
+ def processing_time_ms
89
+ data['processing_time_ms']
90
+ end
91
+
92
+ # Bounding box / position of the detected region, if the API returned one.
93
+ # @return [Hash, nil]
94
+ def position
95
+ data['position']
96
+ end
97
+
98
+ # Raw capture timestamp (used by recent/search results).
99
+ # @return [String, nil]
100
+ def timestamp
101
+ data['timestamp'] || data['created']
102
+ end
103
+
104
+ # Line-level data array (alias for lines_json).
105
+ # @return [Array<Hash>]
106
+ def line_data
107
+ lines_json
108
+ end
109
+
110
+ # Word-level data array, if present.
111
+ # @return [Array<Hash>]
112
+ def word_data
113
+ data['word_data'] || []
114
+ end
115
+
116
+ # Is content printed (vs handwritten)?
117
+ # @return [Boolean]
118
+ def printed?
119
+ data['is_printed'] == true
120
+ end
121
+
122
+ # Is content handwritten?
123
+ # @return [Boolean]
124
+ def handwritten?
125
+ data['is_handwritten'] == true
126
+ end
127
+
128
+ # Contains chart?
129
+ # @return [Boolean]
130
+ def chart?
131
+ data['contains_chart'] == true
132
+ end
133
+
134
+ # Contains diagram?
135
+ # @return [Boolean]
136
+ def diagram?
137
+ data['contains_diagram'] == true
138
+ end
139
+
140
+ # Contains table?
141
+ # @return [Boolean]
142
+ def table?
143
+ data['contains_table'] == true
144
+ end
145
+
146
+ # Get metadata
147
+ # @return [Hash]
148
+ def metadata
149
+ data['metadata'] || {}
150
+ end
151
+
152
+ # Get tags
153
+ # @return [Array<String>]
154
+ def tags
155
+ data['tags'] || []
156
+ end
157
+
158
+ # --- Line-by-line data ---
159
+
160
+ # Get line-by-line data with bounding boxes
161
+ #
162
+ # Requires snap(..., include_line_data: true)
163
+ #
164
+ # @return [Array<Line>] array of line objects
165
+ def lines
166
+ return [] unless data['line_data']
167
+
168
+ @lines ||= data['line_data'].map { |line_data| Line.new(line_data) }
169
+ end
170
+
171
+ # Get lines as JSON array (raw data)
172
+ # @return [Array<Hash>]
173
+ def lines_json
174
+ data['line_data'] || []
175
+ end
176
+
177
+ # Line data structure for bounding boxes and confidence
178
+ class Line
179
+ attr_reader :data
180
+
181
+ def initialize(data)
182
+ @data = data
183
+ end
184
+
185
+ # Line text content
186
+ # @return [String]
187
+ def text
188
+ data['text'] || ''
189
+ end
190
+
191
+ # Line confidence
192
+ # @return [Float]
193
+ def confidence
194
+ data['confidence'] || 0.0
195
+ end
196
+
197
+ # Bounding box coordinates [x, y, width, height]
198
+ # @return [Array<Integer>, nil]
199
+ def bbox
200
+ data['bbox']
201
+ end
202
+
203
+ alias bounding_box bbox
204
+
205
+ # Is this line handwritten?
206
+ # @return [Boolean]
207
+ def handwritten?
208
+ data['type'] == 'handwriting'
209
+ end
210
+
211
+ # Is this line printed?
212
+ # @return [Boolean]
213
+ def printed?
214
+ %w[printed print].include?(data['type'])
215
+ end
216
+
217
+ # LaTeX for this line
218
+ # @return [String, nil]
219
+ def latex
220
+ data['latex']
221
+ end
222
+
223
+ # MathML for this line
224
+ # @return [String, nil]
225
+ def mathml
226
+ data['mathml']
227
+ end
228
+
229
+ # Word-level data (if available)
230
+ # @return [Array<Word>]
231
+ def words
232
+ return [] unless data['words']
233
+
234
+ @words ||= data['words'].map { |word_data| Word.new(word_data) }
235
+ end
236
+
237
+ # Convert to hash
238
+ # @return [Hash]
239
+ def to_h
240
+ data
241
+ end
242
+
243
+ # Inspect
244
+ # @return [String]
245
+ def inspect
246
+ "#<Mathpix::Result::Line text=\"#{text&.[](0..30)}\" confidence=#{confidence}>"
247
+ end
248
+ end
249
+
250
+ # Word data structure for fine-grained bounding boxes
251
+ class Word
252
+ attr_reader :data
253
+
254
+ def initialize(data)
255
+ @data = data
256
+ end
257
+
258
+ # Word text
259
+ # @return [String]
260
+ def text
261
+ data['text'] || ''
262
+ end
263
+
264
+ # Word confidence
265
+ # @return [Float]
266
+ def confidence
267
+ data['confidence'] || 0.0
268
+ end
269
+
270
+ # Word bounding box
271
+ # @return [Array<Integer>, nil]
272
+ def bbox
273
+ data['bbox']
274
+ end
275
+
276
+ # Convert to hash
277
+ # @return [Hash]
278
+ def to_h
279
+ data
280
+ end
281
+
282
+ # Inspect
283
+ # @return [String]
284
+ def inspect
285
+ "#<Mathpix::Result::Word text=\"#{text}\" confidence=#{confidence}>"
286
+ end
287
+ end
288
+
289
+ # Convert to hash
290
+ # @return [Hash]
291
+ def to_h
292
+ data
293
+ end
294
+
295
+ # Convert to JSON
296
+ # @return [String]
297
+ def to_json(*)
298
+ data.to_json(*)
299
+ end
300
+
301
+ # Inspect
302
+ # @return [String]
303
+ def inspect
304
+ "#<Mathpix::Result confidence=#{confidence} text=#{text&.[](0..50)}>"
305
+ end
306
+
307
+ # --- Chemistry-specific methods ---
308
+
309
+ # SMILES notation (chemistry)
310
+ # @return [String, nil]
311
+ def smiles
312
+ text if chemistry?
313
+ end
314
+
315
+ # InChI notation (chemistry)
316
+ # @return [String, nil]
317
+ def inchi
318
+ data['inchi']
319
+ end
320
+
321
+ # InChI Key (chemistry)
322
+ # @return [String, nil]
323
+ def inchikey
324
+ data['inchikey']
325
+ end
326
+
327
+ # Molecular formula
328
+ # @return [String, nil]
329
+ def molecular_formula
330
+ data['molecular_formula']
331
+ end
332
+
333
+ # Molecular name
334
+ # @return [String, nil]
335
+ def molecular_name
336
+ data['molecular_name']
337
+ end
338
+
339
+ # Molecular weight
340
+ # @return [Float, nil]
341
+ def molecular_weight
342
+ data['molecular_weight']
343
+ end
344
+
345
+ # Has stereochemistry?
346
+ # @return [Boolean]
347
+ def stereochemistry?
348
+ data['has_stereochemistry'] == true || smiles&.include?('@')
349
+ end
350
+
351
+ # Is this a chemistry result?
352
+ # @return [Boolean]
353
+ def chemistry?
354
+ molecular_formula || inchi || smiles&.match?(%r{^[A-Za-z0-9@\[\]()=#+\-\\/.]+$})
355
+ end
356
+
357
+ # --- Success/Failure ---
358
+
359
+ # Was capture successful?
360
+ # @return [Boolean]
361
+ def success?
362
+ !text.nil? && confidence >= 0.5
363
+ end
364
+
365
+ # Was capture a failure?
366
+ # @return [Boolean]
367
+ def failure?
368
+ !success?
369
+ end
370
+
371
+ # Execute block if successful
372
+ # @yield [self]
373
+ # @return [self]
374
+ def on_success
375
+ yield self if success?
376
+ self
377
+ end
378
+
379
+ # Execute block if failed
380
+ # @yield [self]
381
+ # @return [self]
382
+ def on_failure
383
+ yield self if failure?
384
+ self
385
+ end
386
+ end
387
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mathpix
4
+ VERSION = '1.0.0'
5
+ end
data/lib/mathpix.rb ADDED
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'openssl'
5
+ require 'json'
6
+ require 'base64'
7
+
8
+ require_relative 'mathpix/version'
9
+ require_relative 'mathpix/errors'
10
+ require_relative 'mathpix/configuration'
11
+ require_relative 'mathpix/result'
12
+ require_relative 'mathpix/client'
13
+ require_relative 'mathpix/document'
14
+
15
+ # Mathpix OCR engine for the MCP server.
16
+ #
17
+ # The classes under Mathpix:: are the engine the MCP tools delegate to
18
+ # (see lib/mathpix/mcp). Only configuration and the shared client instance are
19
+ # exposed at the top level — there is no general-purpose client API.
20
+ module Mathpix
21
+ class << self
22
+ # Configure the Mathpix client.
23
+ #
24
+ # @yield [Configuration] config object
25
+ # @example
26
+ # Mathpix.configure do |config|
27
+ # config.app_id = ENV['MATHPIX_APP_ID']
28
+ # config.app_key = ENV['MATHPIX_APP_KEY']
29
+ # end
30
+ def configure
31
+ yield configuration
32
+ end
33
+
34
+ # Current configuration.
35
+ # @return [Configuration]
36
+ def configuration
37
+ @configuration ||= Configuration.new
38
+ end
39
+
40
+ # Shared client instance used by the MCP tools.
41
+ # @return [Client]
42
+ def client
43
+ @client ||= Client.new(configuration)
44
+ end
45
+
46
+ # Reset configuration and client (mainly for tests).
47
+ def reset!
48
+ @configuration = Configuration.new
49
+ @client = nil
50
+ end
51
+ end
52
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mathpix-mcp
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Georgios Douzas
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: mcp
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.9.2
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 0.9.2
40
+ - !ruby/object:Gem::Dependency
41
+ name: puma
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 8.0.2
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 8.0.2
54
+ - !ruby/object:Gem::Dependency
55
+ name: rack
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.1'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.1'
68
+ description: |
69
+ A Model Context Protocol server for Mathpix OCR, over stdio or Streamable
70
+ HTTP (bearer-token auth). Exposes tools to convert images and PDF/DOCX/PPTX
71
+ documents to LaTeX and Markdown, with descriptive errors and optional file
72
+ output for large results.
73
+ email:
74
+ - georgios.douzas@gmail.com
75
+ executables:
76
+ - mathpix-mcp
77
+ - mathpix-mcp-http
78
+ extensions: []
79
+ extra_rdoc_files: []
80
+ files:
81
+ - CHANGELOG.md
82
+ - LICENSE
83
+ - README.md
84
+ - bin/mathpix-mcp
85
+ - bin/mathpix-mcp-http
86
+ - config.ru
87
+ - lib/mathpix.rb
88
+ - lib/mathpix/client.rb
89
+ - lib/mathpix/configuration.rb
90
+ - lib/mathpix/document.rb
91
+ - lib/mathpix/errors.rb
92
+ - lib/mathpix/mcp.rb
93
+ - lib/mathpix/mcp/base_tool.rb
94
+ - lib/mathpix/mcp/http_app.rb
95
+ - lib/mathpix/mcp/server.rb
96
+ - lib/mathpix/mcp/tools/batch_convert_tool.rb
97
+ - lib/mathpix/mcp/tools/check_document_status_tool.rb
98
+ - lib/mathpix/mcp/tools/convert_document_tool.rb
99
+ - lib/mathpix/mcp/tools/convert_image_tool.rb
100
+ - lib/mathpix/mcp/tools/convert_strokes_tool.rb
101
+ - lib/mathpix/mcp/tools/get_account_info_tool.rb
102
+ - lib/mathpix/mcp/tools/get_usage_tool.rb
103
+ - lib/mathpix/mcp/tools/list_formats_tool.rb
104
+ - lib/mathpix/mcp/tools/search_results_tool.rb
105
+ - lib/mathpix/result.rb
106
+ - lib/mathpix/version.rb
107
+ homepage: https://github.com/georgedouzas/mathpix-mcp
108
+ licenses:
109
+ - MIT
110
+ metadata:
111
+ rubygems_mfa_required: 'true'
112
+ source_code_uri: https://github.com/georgedouzas/mathpix-mcp
113
+ changelog_uri: https://github.com/georgedouzas/mathpix-mcp/blob/main/CHANGELOG.md
114
+ bug_tracker_uri: https://github.com/georgedouzas/mathpix-mcp/issues
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 3.2.0
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.6.9
130
+ specification_version: 4
131
+ summary: Mathpix OCR MCP server (stdio + HTTP)
132
+ test_files: []