mathpix 0.1.0 → 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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +72 -0
- data/README.md +115 -2
- data/SECURITY.md +1 -1
- data/bin/mathpix-mcp +55 -0
- data/lib/mathpix/batch.rb +7 -8
- data/lib/mathpix/batched_document_conversion.rb +238 -0
- data/lib/mathpix/client.rb +33 -27
- data/lib/mathpix/configuration.rb +5 -9
- data/lib/mathpix/conversion.rb +2 -6
- data/lib/mathpix/document.rb +47 -12
- data/lib/mathpix/document_batcher.rb +191 -0
- data/lib/mathpix/mcp/auth/oauth_provider.rb +8 -9
- data/lib/mathpix/mcp/base_tool.rb +8 -5
- data/lib/mathpix/mcp/elicitations/ambiguity_elicitation.rb +8 -11
- data/lib/mathpix/mcp/elicitations/base_elicitation.rb +2 -0
- data/lib/mathpix/mcp/elicitations/confidence_elicitation.rb +2 -1
- data/lib/mathpix/mcp/elicitations.rb +1 -1
- data/lib/mathpix/mcp/middleware/cors_middleware.rb +2 -6
- data/lib/mathpix/mcp/middleware/oauth_middleware.rb +2 -6
- data/lib/mathpix/mcp/middleware/rate_limiting_middleware.rb +19 -18
- data/lib/mathpix/mcp/resources/formats_list_resource.rb +54 -54
- data/lib/mathpix/mcp/resources/hierarchical_router.rb +9 -18
- data/lib/mathpix/mcp/resources/latest_snip_resource.rb +22 -22
- data/lib/mathpix/mcp/resources/recent_snips_resource.rb +11 -10
- data/lib/mathpix/mcp/resources/snip_stats_resource.rb +14 -12
- data/lib/mathpix/mcp/server.rb +18 -18
- data/lib/mathpix/mcp/tools/batch_convert_tool.rb +31 -37
- data/lib/mathpix/mcp/tools/check_document_status_tool.rb +5 -5
- data/lib/mathpix/mcp/tools/convert_document_tool.rb +15 -14
- data/lib/mathpix/mcp/tools/convert_image_tool.rb +15 -14
- data/lib/mathpix/mcp/tools/convert_strokes_tool.rb +13 -13
- data/lib/mathpix/mcp/tools/get_account_info_tool.rb +1 -1
- data/lib/mathpix/mcp/tools/get_usage_tool.rb +5 -7
- data/lib/mathpix/mcp/tools/list_formats_tool.rb +30 -30
- data/lib/mathpix/mcp/tools/search_results_tool.rb +13 -14
- data/lib/mathpix/mcp/transports/http_streaming_transport.rb +129 -118
- data/lib/mathpix/mcp/transports/sse_stream_handler.rb +37 -35
- data/lib/mathpix/result.rb +3 -2
- data/lib/mathpix/version.rb +1 -1
- data/lib/mathpix.rb +3 -1
- metadata +75 -12
@@ -19,18 +19,14 @@ module Mathpix
|
|
19
19
|
origin = env['HTTP_ORIGIN']
|
20
20
|
|
21
21
|
# Handle preflight OPTIONS request
|
22
|
-
if env['REQUEST_METHOD'] == 'OPTIONS'
|
23
|
-
return preflight_response(origin)
|
24
|
-
end
|
22
|
+
return preflight_response(origin) if env['REQUEST_METHOD'] == 'OPTIONS'
|
25
23
|
|
26
24
|
# Call app and add CORS headers
|
27
25
|
status, headers, body = @app.call(env)
|
28
26
|
|
29
27
|
# For wildcard '*', always add CORS headers even without Origin
|
30
28
|
# For specific origins, only add when Origin header is present and allowed
|
31
|
-
if @allowed_origins.include?('*') || (origin && origin_allowed?(origin))
|
32
|
-
add_cors_headers(headers, origin)
|
33
|
-
end
|
29
|
+
add_cors_headers(headers, origin) if @allowed_origins.include?('*') || (origin && origin_allowed?(origin))
|
34
30
|
|
35
31
|
[status, headers, body]
|
36
32
|
end
|
@@ -18,9 +18,7 @@ module Mathpix
|
|
18
18
|
# Extract and validate token
|
19
19
|
token = extract_token(env)
|
20
20
|
|
21
|
-
if token.nil?
|
22
|
-
return unauthorized_response('missing_token')
|
23
|
-
end
|
21
|
+
return unauthorized_response('missing_token') if token.nil?
|
24
22
|
|
25
23
|
begin
|
26
24
|
payload = @oauth_provider.validate_token(token)
|
@@ -39,9 +37,7 @@ module Mathpix
|
|
39
37
|
def extract_token(env)
|
40
38
|
# Try Bearer token
|
41
39
|
auth_header = env['HTTP_AUTHORIZATION']
|
42
|
-
if auth_header&.start_with?('Bearer ')
|
43
|
-
return auth_header.sub('Bearer ', '')
|
44
|
-
end
|
40
|
+
return auth_header.sub('Bearer ', '') if auth_header&.start_with?('Bearer ')
|
45
41
|
|
46
42
|
# Try X-API-Key header
|
47
43
|
env['HTTP_X_API_KEY']
|
@@ -27,15 +27,13 @@ module Mathpix
|
|
27
27
|
@window = window
|
28
28
|
@cleanup_thread = start_cleanup_thread unless ENV['RACK_ENV'] == 'test'
|
29
29
|
|
30
|
-
|
30
|
+
warn "[RATE LIMIT] Middleware initialized: object_id=#{object_id}" if ENV['RACK_ENV'] == 'test'
|
31
31
|
end
|
32
32
|
|
33
33
|
def call(env)
|
34
34
|
# Exempt /health endpoint from rate limiting (monitoring endpoint)
|
35
35
|
request_path = env['PATH_INFO'] || env['REQUEST_PATH']
|
36
|
-
if request_path == '/health'
|
37
|
-
return @app.call(env)
|
38
|
-
end
|
36
|
+
return @app.call(env) if request_path == '/health'
|
39
37
|
|
40
38
|
client_id = extract_client_id(env)
|
41
39
|
|
@@ -43,7 +41,9 @@ module Mathpix
|
|
43
41
|
if rate_limited?(client_id)
|
44
42
|
retry_after = time_until_reset(client_id)
|
45
43
|
bucket = @@buckets[client_id]
|
46
|
-
|
44
|
+
if ENV['RACK_ENV'] == 'test'
|
45
|
+
warn "[RATE LIMIT] LIMITING client #{client_id}, Count: #{bucket[:count]}/#{@limit}"
|
46
|
+
end
|
47
47
|
return rate_limit_response(retry_after)
|
48
48
|
end
|
49
49
|
|
@@ -53,7 +53,7 @@ module Mathpix
|
|
53
53
|
# Debug: show count AFTER incrementing
|
54
54
|
if ENV['RACK_ENV'] == 'test'
|
55
55
|
bucket = @@buckets[client_id]
|
56
|
-
|
56
|
+
warn "[RATE LIMIT] Client: #{client_id}, Count: #{bucket[:count]}/#{@limit}"
|
57
57
|
end
|
58
58
|
|
59
59
|
@app.call(env)
|
@@ -70,7 +70,8 @@ module Mathpix
|
|
70
70
|
|
71
71
|
def rate_limited?(client_id)
|
72
72
|
bucket = @@buckets[client_id]
|
73
|
-
return false unless bucket
|
73
|
+
return false unless bucket # Not rate limited if no bucket yet
|
74
|
+
|
74
75
|
bucket[:count] >= @limit
|
75
76
|
end
|
76
77
|
|
@@ -83,7 +84,7 @@ module Mathpix
|
|
83
84
|
bucket = @@buckets[client_id]
|
84
85
|
old_count = bucket[:count]
|
85
86
|
bucket[:count] += 1
|
86
|
-
|
87
|
+
warn "[RATE LIMIT] record_request: #{old_count} -> #{bucket[:count]}" if ENV['RACK_ENV'] == 'test'
|
87
88
|
end
|
88
89
|
end
|
89
90
|
|
@@ -104,10 +105,10 @@ module Mathpix
|
|
104
105
|
'X-RateLimit-Reset' => (Time.now + retry_after).to_i.to_s
|
105
106
|
},
|
106
107
|
[JSON.generate({
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
108
|
+
error: 'rate_limit_exceeded',
|
109
|
+
message: 'Too many requests',
|
110
|
+
retry_after: retry_after
|
111
|
+
})]
|
111
112
|
]
|
112
113
|
end
|
113
114
|
|
@@ -126,12 +127,12 @@ module Mathpix
|
|
126
127
|
|
127
128
|
def cleanup_expired_buckets
|
128
129
|
now = Time.now
|
129
|
-
@buckets.each_pair do |
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
130
|
+
@buckets.each_pair do |_client_id, bucket|
|
131
|
+
next unless bucket[:reset_at] <= now
|
132
|
+
|
133
|
+
# Reset the bucket instead of deleting
|
134
|
+
bucket[:count] = 0
|
135
|
+
bucket[:reset_at] = now + @window
|
135
136
|
end
|
136
137
|
end
|
137
138
|
end
|
@@ -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(
|
17
|
+
def self.fetch(_client = nil)
|
18
18
|
formats = {
|
19
19
|
image_formats: [
|
20
20
|
{
|
21
|
-
name:
|
22
|
-
description:
|
23
|
-
use_case:
|
21
|
+
name: 'latex_styled',
|
22
|
+
description: 'LaTeX with styling commands',
|
23
|
+
use_case: 'Typesetting, display'
|
24
24
|
},
|
25
25
|
{
|
26
|
-
name:
|
27
|
-
description:
|
28
|
-
use_case:
|
26
|
+
name: 'text',
|
27
|
+
description: 'Plain text representation',
|
28
|
+
use_case: 'Simple text extraction'
|
29
29
|
},
|
30
30
|
{
|
31
|
-
name:
|
32
|
-
description:
|
33
|
-
use_case:
|
31
|
+
name: 'latex_list',
|
32
|
+
description: 'Array of LaTeX expressions',
|
33
|
+
use_case: 'Multiple equations'
|
34
34
|
},
|
35
35
|
{
|
36
|
-
name:
|
37
|
-
description:
|
38
|
-
use_case:
|
36
|
+
name: 'mathml',
|
37
|
+
description: 'MathML markup language',
|
38
|
+
use_case: 'Web display, accessibility'
|
39
39
|
},
|
40
40
|
{
|
41
|
-
name:
|
42
|
-
description:
|
43
|
-
use_case:
|
41
|
+
name: 'asciimath',
|
42
|
+
description: 'AsciiMath notation',
|
43
|
+
use_case: 'Simple math notation'
|
44
44
|
},
|
45
45
|
{
|
46
|
-
name:
|
47
|
-
description:
|
48
|
-
use_case:
|
46
|
+
name: 'text_display',
|
47
|
+
description: 'Display-style text',
|
48
|
+
use_case: 'Large equation display'
|
49
49
|
},
|
50
50
|
{
|
51
|
-
name:
|
52
|
-
description:
|
53
|
-
use_case:
|
51
|
+
name: 'latex_simplified',
|
52
|
+
description: 'Simplified LaTeX',
|
53
|
+
use_case: 'Basic LaTeX output'
|
54
54
|
},
|
55
55
|
{
|
56
|
-
name:
|
57
|
-
description:
|
58
|
-
use_case:
|
56
|
+
name: 'data',
|
57
|
+
description: 'Full response with metadata',
|
58
|
+
use_case: 'Complete API response'
|
59
59
|
},
|
60
60
|
{
|
61
|
-
name:
|
62
|
-
description:
|
63
|
-
use_case:
|
61
|
+
name: 'html',
|
62
|
+
description: 'HTML markup',
|
63
|
+
use_case: 'Web integration'
|
64
64
|
}
|
65
65
|
],
|
66
66
|
document_formats: [
|
67
67
|
{
|
68
|
-
name:
|
69
|
-
description:
|
70
|
-
use_case:
|
68
|
+
name: 'markdown',
|
69
|
+
description: 'Markdown document format',
|
70
|
+
use_case: 'Note-taking, documentation'
|
71
71
|
},
|
72
72
|
{
|
73
|
-
name:
|
74
|
-
description:
|
75
|
-
use_case:
|
73
|
+
name: 'latex',
|
74
|
+
description: 'LaTeX document format',
|
75
|
+
use_case: 'Academic papers, typesetting'
|
76
76
|
},
|
77
77
|
{
|
78
|
-
name:
|
79
|
-
description:
|
80
|
-
use_case:
|
78
|
+
name: 'html',
|
79
|
+
description: 'HTML document format',
|
80
|
+
use_case: 'Web publishing'
|
81
81
|
},
|
82
82
|
{
|
83
|
-
name:
|
84
|
-
description:
|
85
|
-
use_case:
|
83
|
+
name: 'docx',
|
84
|
+
description: 'Microsoft Word document',
|
85
|
+
use_case: 'Word processing'
|
86
86
|
},
|
87
87
|
{
|
88
|
-
name:
|
89
|
-
description:
|
90
|
-
use_case:
|
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:
|
97
|
-
mime_type:
|
96
|
+
uri: 'mathpix://formats/list',
|
97
|
+
mime_type: 'application/json',
|
98
98
|
content: JSON.pretty_generate({
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
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
|
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
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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(
|
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:
|
23
|
-
mime_type:
|
22
|
+
uri: 'mathpix://snip/recent/latest',
|
23
|
+
mime_type: 'application/json',
|
24
24
|
content: JSON.pretty_generate({
|
25
|
-
|
26
|
-
|
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:
|
33
|
-
mime_type:
|
32
|
+
uri: 'mathpix://snip/recent/latest',
|
33
|
+
mime_type: 'application/json',
|
34
34
|
content: JSON.pretty_generate({
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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:
|
50
|
-
mime_type:
|
49
|
+
uri: 'mathpix://snip/recent/latest',
|
50
|
+
mime_type: 'application/json',
|
51
51
|
content: JSON.pretty_generate({
|
52
|
-
|
53
|
-
|
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:
|
43
|
+
mime_type: 'application/json',
|
44
44
|
content: JSON.pretty_generate({
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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:
|
54
|
+
mime_type: 'application/json',
|
55
55
|
content: JSON.pretty_generate({
|
56
|
-
|
57
|
-
|
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:
|
31
|
-
mime_type:
|
30
|
+
uri: 'mathpix://snip/stats',
|
31
|
+
mime_type: 'application/json',
|
32
32
|
content: JSON.pretty_generate({
|
33
|
-
|
34
|
-
|
35
|
-
|
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:
|
41
|
-
mime_type:
|
40
|
+
uri: 'mathpix://snip/stats',
|
41
|
+
mime_type: 'application/json',
|
42
42
|
content: JSON.pretty_generate({
|
43
|
-
|
44
|
-
|
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
|
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
|
}
|
data/lib/mathpix/mcp/server.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
begin
|
4
4
|
require 'mcp'
|
5
|
-
require 'mcp/transports/stdio'
|
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:
|
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:
|
148
|
-
name:
|
149
|
-
description:
|
150
|
-
mime_type:
|
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:
|
154
|
-
name:
|
155
|
-
description:
|
156
|
-
mime_type:
|
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:
|
160
|
-
name:
|
161
|
-
description:
|
162
|
-
mime_type:
|
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:
|
166
|
-
name:
|
167
|
-
description:
|
168
|
-
mime_type:
|
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
|