mathpix 0.1.1 → 0.1.2

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +53 -0
  3. data/README.md +114 -1
  4. data/lib/mathpix/batch.rb +7 -8
  5. data/lib/mathpix/batched_document_conversion.rb +238 -0
  6. data/lib/mathpix/client.rb +33 -27
  7. data/lib/mathpix/configuration.rb +5 -9
  8. data/lib/mathpix/conversion.rb +2 -6
  9. data/lib/mathpix/document.rb +47 -12
  10. data/lib/mathpix/document_batcher.rb +191 -0
  11. data/lib/mathpix/mcp/auth/oauth_provider.rb +8 -9
  12. data/lib/mathpix/mcp/base_tool.rb +8 -5
  13. data/lib/mathpix/mcp/elicitations/ambiguity_elicitation.rb +8 -11
  14. data/lib/mathpix/mcp/elicitations/base_elicitation.rb +2 -0
  15. data/lib/mathpix/mcp/elicitations/confidence_elicitation.rb +2 -1
  16. data/lib/mathpix/mcp/elicitations.rb +1 -1
  17. data/lib/mathpix/mcp/middleware/cors_middleware.rb +2 -6
  18. data/lib/mathpix/mcp/middleware/oauth_middleware.rb +2 -6
  19. data/lib/mathpix/mcp/middleware/rate_limiting_middleware.rb +19 -18
  20. data/lib/mathpix/mcp/resources/formats_list_resource.rb +54 -54
  21. data/lib/mathpix/mcp/resources/hierarchical_router.rb +9 -18
  22. data/lib/mathpix/mcp/resources/latest_snip_resource.rb +22 -22
  23. data/lib/mathpix/mcp/resources/recent_snips_resource.rb +11 -10
  24. data/lib/mathpix/mcp/resources/snip_stats_resource.rb +14 -12
  25. data/lib/mathpix/mcp/server.rb +18 -18
  26. data/lib/mathpix/mcp/tools/batch_convert_tool.rb +31 -37
  27. data/lib/mathpix/mcp/tools/check_document_status_tool.rb +5 -5
  28. data/lib/mathpix/mcp/tools/convert_document_tool.rb +15 -14
  29. data/lib/mathpix/mcp/tools/convert_image_tool.rb +15 -14
  30. data/lib/mathpix/mcp/tools/convert_strokes_tool.rb +13 -13
  31. data/lib/mathpix/mcp/tools/get_account_info_tool.rb +1 -1
  32. data/lib/mathpix/mcp/tools/get_usage_tool.rb +5 -7
  33. data/lib/mathpix/mcp/tools/list_formats_tool.rb +30 -30
  34. data/lib/mathpix/mcp/tools/search_results_tool.rb +13 -14
  35. data/lib/mathpix/mcp/transports/http_streaming_transport.rb +129 -118
  36. data/lib/mathpix/mcp/transports/sse_stream_handler.rb +37 -35
  37. data/lib/mathpix/result.rb +3 -2
  38. data/lib/mathpix/version.rb +1 -1
  39. data/lib/mathpix.rb +3 -1
  40. metadata +60 -2
@@ -14,97 +14,97 @@ module Mathpix
14
14
  #
15
15
  # @param client [Mathpix::Client] optional client (not used, kept for consistency)
16
16
  # @return [Hash] resource content
17
- def self.fetch(client = nil)
17
+ def self.fetch(_client = nil)
18
18
  formats = {
19
19
  image_formats: [
20
20
  {
21
- name: "latex_styled",
22
- description: "LaTeX with styling commands",
23
- use_case: "Typesetting, display"
21
+ name: 'latex_styled',
22
+ description: 'LaTeX with styling commands',
23
+ use_case: 'Typesetting, display'
24
24
  },
25
25
  {
26
- name: "text",
27
- description: "Plain text representation",
28
- use_case: "Simple text extraction"
26
+ name: 'text',
27
+ description: 'Plain text representation',
28
+ use_case: 'Simple text extraction'
29
29
  },
30
30
  {
31
- name: "latex_list",
32
- description: "Array of LaTeX expressions",
33
- use_case: "Multiple equations"
31
+ name: 'latex_list',
32
+ description: 'Array of LaTeX expressions',
33
+ use_case: 'Multiple equations'
34
34
  },
35
35
  {
36
- name: "mathml",
37
- description: "MathML markup language",
38
- use_case: "Web display, accessibility"
36
+ name: 'mathml',
37
+ description: 'MathML markup language',
38
+ use_case: 'Web display, accessibility'
39
39
  },
40
40
  {
41
- name: "asciimath",
42
- description: "AsciiMath notation",
43
- use_case: "Simple math notation"
41
+ name: 'asciimath',
42
+ description: 'AsciiMath notation',
43
+ use_case: 'Simple math notation'
44
44
  },
45
45
  {
46
- name: "text_display",
47
- description: "Display-style text",
48
- use_case: "Large equation display"
46
+ name: 'text_display',
47
+ description: 'Display-style text',
48
+ use_case: 'Large equation display'
49
49
  },
50
50
  {
51
- name: "latex_simplified",
52
- description: "Simplified LaTeX",
53
- use_case: "Basic LaTeX output"
51
+ name: 'latex_simplified',
52
+ description: 'Simplified LaTeX',
53
+ use_case: 'Basic LaTeX output'
54
54
  },
55
55
  {
56
- name: "data",
57
- description: "Full response with metadata",
58
- use_case: "Complete API response"
56
+ name: 'data',
57
+ description: 'Full response with metadata',
58
+ use_case: 'Complete API response'
59
59
  },
60
60
  {
61
- name: "html",
62
- description: "HTML markup",
63
- use_case: "Web integration"
61
+ name: 'html',
62
+ description: 'HTML markup',
63
+ use_case: 'Web integration'
64
64
  }
65
65
  ],
66
66
  document_formats: [
67
67
  {
68
- name: "markdown",
69
- description: "Markdown document format",
70
- use_case: "Note-taking, documentation"
68
+ name: 'markdown',
69
+ description: 'Markdown document format',
70
+ use_case: 'Note-taking, documentation'
71
71
  },
72
72
  {
73
- name: "latex",
74
- description: "LaTeX document format",
75
- use_case: "Academic papers, typesetting"
73
+ name: 'latex',
74
+ description: 'LaTeX document format',
75
+ use_case: 'Academic papers, typesetting'
76
76
  },
77
77
  {
78
- name: "html",
79
- description: "HTML document format",
80
- use_case: "Web publishing"
78
+ name: 'html',
79
+ description: 'HTML document format',
80
+ use_case: 'Web publishing'
81
81
  },
82
82
  {
83
- name: "docx",
84
- description: "Microsoft Word document",
85
- use_case: "Word processing"
83
+ name: 'docx',
84
+ description: 'Microsoft Word document',
85
+ use_case: 'Word processing'
86
86
  },
87
87
  {
88
- name: "tex.zip",
89
- description: "LaTeX with figures (zipped)",
90
- use_case: "Complete LaTeX projects"
88
+ name: 'tex.zip',
89
+ description: 'LaTeX with figures (zipped)',
90
+ use_case: 'Complete LaTeX projects'
91
91
  }
92
92
  ]
93
93
  }
94
94
 
95
95
  {
96
- uri: "mathpix://formats/list",
97
- mime_type: "application/json",
96
+ uri: 'mathpix://formats/list',
97
+ mime_type: 'application/json',
98
98
  content: JSON.pretty_generate({
99
- success: true,
100
- image_formats_count: formats[:image_formats].length,
101
- document_formats_count: formats[:document_formats].length,
102
- formats: formats,
103
- usage: {
104
- image_capture: "Use with Mathpix.snap() or ConvertImageTool",
105
- document_conversion: "Use with Mathpix.document() or ConvertDocumentTool"
106
- }
107
- })
99
+ success: true,
100
+ image_formats_count: formats[:image_formats].length,
101
+ document_formats_count: formats[:document_formats].length,
102
+ formats: formats,
103
+ usage: {
104
+ image_capture: 'Use with Mathpix.snap() or ConvertImageTool',
105
+ document_conversion: 'Use with Mathpix.document() or ConvertDocumentTool'
106
+ }
107
+ })
108
108
  }
109
109
  end
110
110
  end
@@ -84,7 +84,7 @@ module Mathpix
84
84
  links[:first] = build_link(path, limit, 0, query_params)
85
85
 
86
86
  # Previous link (if not on first page)
87
- if offset > 0
87
+ if offset.positive?
88
88
  prev_offset = [offset - limit, 0].max
89
89
  links[:prev] = build_link(path, limit, prev_offset, query_params)
90
90
  end
@@ -168,9 +168,7 @@ module Mathpix
168
168
  return false if value == 'false'
169
169
 
170
170
  # Array (comma-separated)
171
- if value.include?(',')
172
- return value.split(',').map(&:strip)
173
- end
171
+ return value.split(',').map(&:strip) if value.include?(',')
174
172
 
175
173
  # String
176
174
  value
@@ -179,9 +177,7 @@ module Mathpix
179
177
  def validate_params(params)
180
178
  # Validate limit
181
179
  if params[:limit]
182
- unless params[:limit].is_a?(Integer)
183
- raise InvalidParameterError, 'limit must be a number'
184
- end
180
+ raise InvalidParameterError, 'limit must be a number' unless params[:limit].is_a?(Integer)
185
181
 
186
182
  unless params[:limit].between?(MIN_LIMIT, MAX_LIMIT)
187
183
  raise InvalidParameterError, "limit must be between #{MIN_LIMIT} and #{MAX_LIMIT}"
@@ -189,18 +185,13 @@ module Mathpix
189
185
  end
190
186
 
191
187
  # Validate offset
192
- if params[:offset]
193
- unless params[:offset].is_a?(Integer)
194
- raise InvalidParameterError, 'offset must be a number'
195
- end
196
- end
188
+ raise InvalidParameterError, 'offset must be a number' if params[:offset] && !params[:offset].is_a?(Integer)
197
189
 
198
190
  # Validate ID format (if present)
199
- if params[:id]
200
- unless params[:id] =~ /^[a-zA-Z0-9_-]+$/
201
- raise InvalidParameterError, 'id must be alphanumeric'
202
- end
203
- end
191
+ return unless params[:id]
192
+ return if params[:id] =~ /^[a-zA-Z0-9_-]+$/
193
+
194
+ raise InvalidParameterError, 'id must be alphanumeric'
204
195
  end
205
196
 
206
197
  def build_link(path, limit, offset, query_params)
@@ -228,7 +219,7 @@ module Mathpix
228
219
  { id: params[:id], line_number: params[:line_number] }
229
220
  end
230
221
 
231
- def list_snips(params)
222
+ def list_snips(_params)
232
223
  []
233
224
  end
234
225
  end
@@ -19,39 +19,39 @@ module Mathpix
19
19
 
20
20
  if recent.empty?
21
21
  {
22
- uri: "mathpix://snip/recent/latest",
23
- mime_type: "application/json",
22
+ uri: 'mathpix://snip/recent/latest',
23
+ mime_type: 'application/json',
24
24
  content: JSON.pretty_generate({
25
- success: false,
26
- message: "No recent captures found"
27
- })
25
+ success: false,
26
+ message: 'No recent captures found'
27
+ })
28
28
  }
29
29
  else
30
30
  result = recent.first
31
31
  {
32
- uri: "mathpix://snip/recent/latest",
33
- mime_type: "application/json",
32
+ uri: 'mathpix://snip/recent/latest',
33
+ mime_type: 'application/json',
34
34
  content: JSON.pretty_generate({
35
- success: true,
36
- id: result.request_id,
37
- created_at: result.timestamp,
38
- latex: result.latex,
39
- text: result.text,
40
- confidence: result.confidence,
41
- is_printed: result.printed?,
42
- is_handwritten: result.handwritten?,
43
- position: result.position
44
- })
35
+ success: true,
36
+ id: result.request_id,
37
+ created_at: result.timestamp,
38
+ latex: result.latex,
39
+ text: result.text,
40
+ confidence: result.confidence,
41
+ is_printed: result.printed?,
42
+ is_handwritten: result.handwritten?,
43
+ position: result.position
44
+ })
45
45
  }
46
46
  end
47
47
  rescue Mathpix::Error => e
48
48
  {
49
- uri: "mathpix://snip/recent/latest",
50
- mime_type: "application/json",
49
+ uri: 'mathpix://snip/recent/latest',
50
+ mime_type: 'application/json',
51
51
  content: JSON.pretty_generate({
52
- success: false,
53
- error: e.message
54
- })
52
+ success: false,
53
+ error: e.message
54
+ })
55
55
  }
56
56
  end
57
57
  end
@@ -40,22 +40,22 @@ module Mathpix
40
40
 
41
41
  {
42
42
  uri: "mathpix://snip/recent?limit=#{limit}",
43
- mime_type: "application/json",
43
+ mime_type: 'application/json',
44
44
  content: JSON.pretty_generate({
45
- success: true,
46
- limit: limit,
47
- count: results.length,
48
- results: results
49
- })
45
+ success: true,
46
+ limit: limit,
47
+ count: results.length,
48
+ results: results
49
+ })
50
50
  }
51
51
  rescue Mathpix::Error => e
52
52
  {
53
53
  uri: "mathpix://snip/recent?limit=#{limit}",
54
- mime_type: "application/json",
54
+ mime_type: 'application/json',
55
55
  content: JSON.pretty_generate({
56
- success: false,
57
- error: e.message
58
- })
56
+ success: false,
57
+ error: e.message
58
+ })
59
59
  }
60
60
  end
61
61
 
@@ -67,6 +67,7 @@ module Mathpix
67
67
  def self.truncate(text, max_length)
68
68
  return nil unless text
69
69
  return text if text.length <= max_length
70
+
70
71
  "#{text[0...max_length]}..."
71
72
  end
72
73
  end
@@ -27,22 +27,22 @@ module Mathpix
27
27
  stats = compute_stats(recent)
28
28
 
29
29
  {
30
- uri: "mathpix://snip/stats",
31
- mime_type: "application/json",
30
+ uri: 'mathpix://snip/stats',
31
+ mime_type: 'application/json',
32
32
  content: JSON.pretty_generate({
33
- success: true,
34
- period: "last_100_captures",
35
- stats: stats
36
- })
33
+ success: true,
34
+ period: 'last_100_captures',
35
+ stats: stats
36
+ })
37
37
  }
38
38
  rescue Mathpix::Error => e
39
39
  {
40
- uri: "mathpix://snip/stats",
41
- mime_type: "application/json",
40
+ uri: 'mathpix://snip/stats',
41
+ mime_type: 'application/json',
42
42
  content: JSON.pretty_generate({
43
- success: false,
44
- error: e.message
45
- })
43
+ success: false,
44
+ error: e.message
45
+ })
46
46
  }
47
47
  end
48
48
 
@@ -67,7 +67,9 @@ module Mathpix
67
67
  average_confidence: avg_confidence.round(3),
68
68
  confidence_distribution: {
69
69
  high: confidences.count { |c| c >= Mathpix::Configuration::CONFIDENCE_HIGH },
70
- medium: confidences.count { |c| c >= Mathpix::Configuration::CONFIDENCE_MEDIUM && c < Mathpix::Configuration::CONFIDENCE_HIGH },
70
+ medium: confidences.count do |c|
71
+ c >= Mathpix::Configuration::CONFIDENCE_MEDIUM && c < Mathpix::Configuration::CONFIDENCE_HIGH
72
+ end,
71
73
  low: confidences.count { |c| c < Mathpix::Configuration::CONFIDENCE_MEDIUM }
72
74
  }
73
75
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  begin
4
4
  require 'mcp'
5
- require 'mcp/transports/stdio' # Transport classes not auto-loaded
5
+ require 'mcp/transports/stdio' # Transport classes not auto-loaded
6
6
  rescue LoadError
7
7
  raise LoadError, <<~ERROR
8
8
  The 'mcp' gem is required for MCP server functionality.
@@ -52,7 +52,7 @@ module Mathpix
52
52
  # @param name [String] server name
53
53
  # @param version [String] server version
54
54
  # @param mathpix_client [Mathpix::Client] optional client instance
55
- def initialize(name: "mathpix", version: Mathpix::VERSION, mathpix_client: nil)
55
+ def initialize(name: 'mathpix', version: Mathpix::VERSION, mathpix_client: nil)
56
56
  @name = name
57
57
  @version = version
58
58
  @mathpix_client = mathpix_client || Mathpix.client
@@ -144,28 +144,28 @@ module Mathpix
144
144
  def resource_specs
145
145
  [
146
146
  {
147
- uri: "mathpix://snip/recent/latest",
148
- name: "latest-snip",
149
- description: "Most recent capture result",
150
- mime_type: "application/json"
147
+ uri: 'mathpix://snip/recent/latest',
148
+ name: 'latest-snip',
149
+ description: 'Most recent capture result',
150
+ mime_type: 'application/json'
151
151
  },
152
152
  {
153
- uri: "mathpix://snip/stats",
154
- name: "snip-stats",
155
- description: "Overall capture statistics",
156
- mime_type: "application/json"
153
+ uri: 'mathpix://snip/stats',
154
+ name: 'snip-stats',
155
+ description: 'Overall capture statistics',
156
+ mime_type: 'application/json'
157
157
  },
158
158
  {
159
- uri: "mathpix://snip/recent?limit=10",
160
- name: "recent-snips",
161
- description: "Recent capture results (default limit 10)",
162
- mime_type: "application/json"
159
+ uri: 'mathpix://snip/recent?limit=10',
160
+ name: 'recent-snips',
161
+ description: 'Recent capture results (default limit 10)',
162
+ mime_type: 'application/json'
163
163
  },
164
164
  {
165
- uri: "mathpix://formats/list",
166
- name: "formats-list",
167
- description: "Available output formats",
168
- mime_type: "application/json"
165
+ uri: 'mathpix://formats/list',
166
+ name: 'formats-list',
167
+ description: 'Available output formats',
168
+ mime_type: 'application/json'
169
169
  }
170
170
  ]
171
171
  end
@@ -12,33 +12,33 @@ module Mathpix
12
12
  #
13
13
  # The geodesic path: official SDK + batch iteration
14
14
  class BatchConvertTool < BaseTool
15
- description "Convert multiple images in batch using Mathpix OCR"
15
+ description 'Convert multiple images in batch using Mathpix OCR'
16
16
 
17
17
  input_schema(
18
18
  properties: {
19
19
  image_paths: {
20
- type: "array",
21
- items: { type: "string" },
22
- description: "Array of image paths or URLs to process"
20
+ type: 'array',
21
+ items: { type: 'string' },
22
+ description: 'Array of image paths or URLs to process'
23
23
  },
24
24
  formats: {
25
- type: "array",
26
- items: { type: "string" },
27
- description: "Output formats for all images: latex, text, mathml, asciimath (default: latex_styled, text)"
25
+ type: 'array',
26
+ items: { type: 'string' },
27
+ description: 'Output formats for all images: latex, text, mathml, asciimath (default: latex_styled, text)'
28
28
  },
29
29
  parallel: {
30
- type: "boolean",
31
- description: "Process images in parallel (default: false)"
30
+ type: 'boolean',
31
+ description: 'Process images in parallel (default: false)'
32
32
  },
33
33
  max_parallel: {
34
- type: "number",
35
- description: "Maximum number of parallel requests (default: 3)"
34
+ type: 'number',
35
+ description: 'Maximum number of parallel requests (default: 3)'
36
36
  }
37
37
  },
38
- required: ["image_paths"]
38
+ required: ['image_paths']
39
39
  )
40
40
 
41
- def self.call(image_paths:, formats: nil, parallel: false, max_parallel: 3, server_context:)
41
+ def self.call(image_paths:, server_context:, formats: nil, parallel: false, max_parallel: 3)
42
42
  safe_execute do
43
43
  client = mathpix_client(server_context)
44
44
 
@@ -51,13 +51,11 @@ module Mathpix
51
51
  end
52
52
 
53
53
  # Process images
54
- results = if parallel
54
+ if parallel
55
55
  # TODO: Implement parallel processing (requires threading)
56
56
  # For now, process sequentially
57
- process_batch_sequential(client, normalized_paths, output_formats)
58
- else
59
- process_batch_sequential(client, normalized_paths, output_formats)
60
57
  end
58
+ results = process_batch_sequential(client, normalized_paths, output_formats)
61
59
 
62
60
  # Format response
63
61
  response_data = {
@@ -76,28 +74,24 @@ module Mathpix
76
74
  end
77
75
  end
78
76
 
79
- private
80
-
81
77
  def self.process_batch_sequential(client, paths, formats)
82
78
  paths.map.with_index do |path, index|
83
- begin
84
- result = client.snap(path, formats: formats)
85
- {
86
- index: index,
87
- image_path: path,
88
- success: true,
89
- latex: result.latex,
90
- text: result.text,
91
- confidence: result.confidence
92
- }
93
- rescue Mathpix::Error => e
94
- {
95
- index: index,
96
- image_path: path,
97
- success: false,
98
- error: e.message
99
- }
100
- end
79
+ result = client.snap(path, formats: formats)
80
+ {
81
+ index: index,
82
+ image_path: path,
83
+ success: true,
84
+ latex: result.latex,
85
+ text: result.text,
86
+ confidence: result.confidence
87
+ }
88
+ rescue Mathpix::Error => e
89
+ {
90
+ index: index,
91
+ image_path: path,
92
+ success: false,
93
+ error: e.message
94
+ }
101
95
  end
102
96
  end
103
97
  end
@@ -12,16 +12,16 @@ module Mathpix
12
12
  #
13
13
  # The geodesic path: official SDK + status polling
14
14
  class CheckDocumentStatusTool < BaseTool
15
- description "Check the status of a document conversion (PDF, DOCX, PPTX)"
15
+ description 'Check the status of a document conversion (PDF, DOCX, PPTX)'
16
16
 
17
17
  input_schema(
18
18
  properties: {
19
19
  conversion_id: {
20
- type: "string",
21
- description: "Document conversion ID returned from convert_document"
20
+ type: 'string',
21
+ description: 'Document conversion ID returned from convert_document'
22
22
  }
23
23
  },
24
- required: ["conversion_id"]
24
+ required: ['conversion_id']
25
25
  )
26
26
 
27
27
  def self.call(conversion_id:, server_context:)
@@ -52,7 +52,7 @@ module Mathpix
52
52
  end
53
53
 
54
54
  # Add error info if failed
55
- if status_data['status'] == 'error' || status_data['status'] == 'failed'
55
+ if %w[error failed].include?(status_data['status'])
56
56
  response_data[:error] = status_data['error']
57
57
  response_data[:error_info] = status_data['error_info']
58
58
  end
@@ -12,36 +12,37 @@ module Mathpix
12
12
  #
13
13
  # The geodesic path: official SDK + Document class delegation
14
14
  class ConvertDocumentTool < BaseTool
15
- description "Convert document (PDF, DOCX, PPTX) to Markdown, LaTeX, HTML, or other formats using Mathpix OCR"
15
+ description 'Convert document (PDF, DOCX, PPTX) to Markdown, LaTeX, HTML, or other formats using Mathpix OCR'
16
16
 
17
17
  input_schema(
18
18
  properties: {
19
19
  document_path: {
20
- type: "string",
21
- description: "Path to document file or URL (PDF, DOCX, or PPTX)"
20
+ type: 'string',
21
+ description: 'Path to document file or URL (PDF, DOCX, or PPTX)'
22
22
  },
23
23
  formats: {
24
- type: "array",
25
- items: { type: "string" },
26
- description: "Output formats: markdown, latex, html, docx (default: markdown)"
24
+ type: 'array',
25
+ items: { type: 'string' },
26
+ description: 'Output formats: markdown, latex, html, docx (default: markdown)'
27
27
  },
28
28
  include_tables: {
29
- type: "boolean",
30
- description: "Include table extraction as HTML"
29
+ type: 'boolean',
30
+ description: 'Include table extraction as HTML'
31
31
  },
32
32
  max_wait: {
33
- type: "number",
34
- description: "Maximum wait time in seconds for conversion (default: 600)"
33
+ type: 'number',
34
+ description: 'Maximum wait time in seconds for conversion (default: 600)'
35
35
  },
36
36
  poll_interval: {
37
- type: "number",
38
- description: "Polling interval in seconds (default: 3.0)"
37
+ type: 'number',
38
+ description: 'Polling interval in seconds (default: 3.0)'
39
39
  }
40
40
  },
41
- required: ["document_path"]
41
+ required: ['document_path']
42
42
  )
43
43
 
44
- def self.call(document_path:, formats: nil, include_tables: false, max_wait: 600, poll_interval: 3.0, server_context:)
44
+ def self.call(document_path:, server_context:, formats: nil, include_tables: false, max_wait: 600,
45
+ poll_interval: 3.0)
45
46
  safe_execute do
46
47
  client = mathpix_client(server_context)
47
48