mathpix 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +52 -0
- data/LICENSE +21 -0
- data/README.md +171 -0
- data/SECURITY.md +137 -0
- data/lib/mathpix/balanced_ternary.rb +86 -0
- data/lib/mathpix/batch.rb +155 -0
- data/lib/mathpix/capture_builder.rb +142 -0
- data/lib/mathpix/chemistry.rb +69 -0
- data/lib/mathpix/client.rb +439 -0
- data/lib/mathpix/configuration.rb +187 -0
- data/lib/mathpix/configuration.rb.backup +125 -0
- data/lib/mathpix/conversion.rb +257 -0
- data/lib/mathpix/document.rb +320 -0
- data/lib/mathpix/errors.rb +78 -0
- data/lib/mathpix/mcp/auth/oauth_provider.rb +346 -0
- data/lib/mathpix/mcp/auth/token_manager.rb +31 -0
- data/lib/mathpix/mcp/auth.rb +18 -0
- data/lib/mathpix/mcp/base_tool.rb +117 -0
- data/lib/mathpix/mcp/elicitations/ambiguity_elicitation.rb +162 -0
- data/lib/mathpix/mcp/elicitations/base_elicitation.rb +141 -0
- data/lib/mathpix/mcp/elicitations/confidence_elicitation.rb +162 -0
- data/lib/mathpix/mcp/elicitations.rb +78 -0
- data/lib/mathpix/mcp/middleware/cors_middleware.rb +94 -0
- data/lib/mathpix/mcp/middleware/oauth_middleware.rb +72 -0
- data/lib/mathpix/mcp/middleware/rate_limiting_middleware.rb +140 -0
- data/lib/mathpix/mcp/middleware.rb +13 -0
- data/lib/mathpix/mcp/resources/formats_list_resource.rb +113 -0
- data/lib/mathpix/mcp/resources/hierarchical_router.rb +237 -0
- data/lib/mathpix/mcp/resources/latest_snip_resource.rb +60 -0
- data/lib/mathpix/mcp/resources/recent_snips_resource.rb +75 -0
- data/lib/mathpix/mcp/resources/snip_stats_resource.rb +78 -0
- data/lib/mathpix/mcp/resources.rb +15 -0
- data/lib/mathpix/mcp/server.rb +174 -0
- data/lib/mathpix/mcp/tools/batch_convert_tool.rb +106 -0
- data/lib/mathpix/mcp/tools/check_document_status_tool.rb +66 -0
- data/lib/mathpix/mcp/tools/convert_document_tool.rb +90 -0
- data/lib/mathpix/mcp/tools/convert_image_tool.rb +91 -0
- data/lib/mathpix/mcp/tools/convert_strokes_tool.rb +82 -0
- data/lib/mathpix/mcp/tools/get_account_info_tool.rb +57 -0
- data/lib/mathpix/mcp/tools/get_usage_tool.rb +62 -0
- data/lib/mathpix/mcp/tools/list_formats_tool.rb +81 -0
- data/lib/mathpix/mcp/tools/search_results_tool.rb +111 -0
- data/lib/mathpix/mcp/transports/http_streaming_transport.rb +622 -0
- data/lib/mathpix/mcp/transports/sse_stream_handler.rb +236 -0
- data/lib/mathpix/mcp/transports.rb +12 -0
- data/lib/mathpix/mcp.rb +52 -0
- data/lib/mathpix/result.rb +364 -0
- data/lib/mathpix/version.rb +22 -0
- data/lib/mathpix.rb +229 -0
- metadata +283 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_tool'
|
4
|
+
|
5
|
+
module Mathpix
|
6
|
+
module MCP
|
7
|
+
module Tools
|
8
|
+
# Get Usage Tool
|
9
|
+
#
|
10
|
+
# Retrieves API usage statistics and account limits
|
11
|
+
# Thin delegate to Mathpix::Client (usage endpoint)
|
12
|
+
#
|
13
|
+
# The geodesic path: official SDK + usage tracking
|
14
|
+
class GetUsageTool < BaseTool
|
15
|
+
description "Get Mathpix API usage statistics and remaining credits"
|
16
|
+
|
17
|
+
input_schema(
|
18
|
+
properties: {
|
19
|
+
detailed: {
|
20
|
+
type: "boolean",
|
21
|
+
description: "Include detailed breakdown by operation type (default: false)"
|
22
|
+
}
|
23
|
+
},
|
24
|
+
required: []
|
25
|
+
)
|
26
|
+
|
27
|
+
def self.call(detailed: false, server_context:)
|
28
|
+
safe_execute do
|
29
|
+
client = mathpix_client(server_context)
|
30
|
+
|
31
|
+
begin
|
32
|
+
# Try to call the usage endpoint
|
33
|
+
usage_data = client.get('/usage')
|
34
|
+
|
35
|
+
response_data = {
|
36
|
+
success: true,
|
37
|
+
usage: {
|
38
|
+
requests_this_month: usage_data['requests_this_month'],
|
39
|
+
requests_remaining: usage_data['requests_remaining'],
|
40
|
+
plan: usage_data['plan'],
|
41
|
+
reset_date: usage_data['reset_date']
|
42
|
+
}
|
43
|
+
}
|
44
|
+
|
45
|
+
if detailed && usage_data['breakdown']
|
46
|
+
response_data[:breakdown] = usage_data['breakdown']
|
47
|
+
end
|
48
|
+
|
49
|
+
json_response(response_data)
|
50
|
+
rescue Mathpix::APIError => e
|
51
|
+
# Handle API-specific errors properly
|
52
|
+
error_response(e)
|
53
|
+
rescue StandardError => e
|
54
|
+
# Handle unexpected errors
|
55
|
+
error_response(e)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_tool'
|
4
|
+
|
5
|
+
module Mathpix
|
6
|
+
module MCP
|
7
|
+
module Tools
|
8
|
+
# List Formats Tool
|
9
|
+
#
|
10
|
+
# Lists all available output formats for Mathpix OCR
|
11
|
+
# Static data from API documentation
|
12
|
+
#
|
13
|
+
# The geodesic path: official SDK + format catalog
|
14
|
+
class ListFormatsTool < BaseTool
|
15
|
+
description "List all available output formats for Mathpix OCR operations"
|
16
|
+
|
17
|
+
input_schema(
|
18
|
+
properties: {
|
19
|
+
category: {
|
20
|
+
type: "string",
|
21
|
+
description: "Filter by category: image, document, or all (default: all)",
|
22
|
+
enum: ["image", "document", "all"]
|
23
|
+
}
|
24
|
+
},
|
25
|
+
required: []
|
26
|
+
)
|
27
|
+
|
28
|
+
def self.call(category: "all", server_context:)
|
29
|
+
safe_execute do
|
30
|
+
# Static format definitions
|
31
|
+
image_formats = [
|
32
|
+
{ name: "latex_styled", description: "LaTeX with styling", type: "image" },
|
33
|
+
{ name: "text", description: "Plain text", type: "image" },
|
34
|
+
{ name: "latex_list", description: "Array of LaTeX expressions", type: "image" },
|
35
|
+
{ name: "mathml", description: "MathML markup", type: "image" },
|
36
|
+
{ name: "asciimath", description: "AsciiMath notation", type: "image" },
|
37
|
+
{ name: "text_display", description: "Display-style text", type: "image" },
|
38
|
+
{ name: "latex_simplified", description: "Simplified LaTeX", type: "image" },
|
39
|
+
{ name: "data", description: "Full response data with metadata", type: "image" },
|
40
|
+
{ name: "html", description: "HTML markup", type: "image" }
|
41
|
+
]
|
42
|
+
|
43
|
+
document_formats = [
|
44
|
+
{ name: "markdown", description: "Markdown format", type: "document" },
|
45
|
+
{ name: "latex", description: "LaTeX document", type: "document" },
|
46
|
+
{ name: "html", description: "HTML document", type: "document" },
|
47
|
+
{ name: "docx", description: "Microsoft Word document", type: "document" },
|
48
|
+
{ name: "tex.zip", description: "LaTeX with figures (zipped)", type: "document" }
|
49
|
+
]
|
50
|
+
|
51
|
+
# Filter by category
|
52
|
+
formats = case category
|
53
|
+
when "image"
|
54
|
+
image_formats
|
55
|
+
when "document"
|
56
|
+
document_formats
|
57
|
+
when "all"
|
58
|
+
image_formats + document_formats
|
59
|
+
else
|
60
|
+
image_formats + document_formats
|
61
|
+
end
|
62
|
+
|
63
|
+
# Format response
|
64
|
+
response_data = {
|
65
|
+
success: true,
|
66
|
+
category: category,
|
67
|
+
count: formats.length,
|
68
|
+
formats: formats,
|
69
|
+
usage: {
|
70
|
+
image_capture: "Use with snap() or ConvertImageTool",
|
71
|
+
document_conversion: "Use with document() or ConvertDocumentTool"
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
json_response(response_data)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_tool'
|
4
|
+
|
5
|
+
module Mathpix
|
6
|
+
module MCP
|
7
|
+
module Tools
|
8
|
+
# Search Results Tool
|
9
|
+
#
|
10
|
+
# Searches recent capture results with optional filtering
|
11
|
+
# Thin delegate to Mathpix::Client#recent with search
|
12
|
+
#
|
13
|
+
# The geodesic path: official SDK + search filtering
|
14
|
+
class SearchResultsTool < BaseTool
|
15
|
+
description "Search recent Mathpix capture results with optional text filtering"
|
16
|
+
|
17
|
+
input_schema(
|
18
|
+
properties: {
|
19
|
+
query: {
|
20
|
+
type: "string",
|
21
|
+
description: "Search query to filter results by LaTeX or text content"
|
22
|
+
},
|
23
|
+
limit: {
|
24
|
+
type: "number",
|
25
|
+
description: "Maximum number of results to return (default: 10, max: 100)"
|
26
|
+
},
|
27
|
+
offset: {
|
28
|
+
type: "number",
|
29
|
+
description: "Offset for pagination (default: 0)"
|
30
|
+
},
|
31
|
+
include_content: {
|
32
|
+
type: "boolean",
|
33
|
+
description: "Include full LaTeX/text content in results (default: false)"
|
34
|
+
}
|
35
|
+
},
|
36
|
+
required: []
|
37
|
+
)
|
38
|
+
|
39
|
+
def self.call(query: nil, limit: 10, offset: 0, include_content: false, server_context:)
|
40
|
+
safe_execute do
|
41
|
+
client = mathpix_client(server_context)
|
42
|
+
|
43
|
+
# Validate and constrain limit
|
44
|
+
limit = [[limit.to_i, 1].max, 100].min
|
45
|
+
|
46
|
+
# Get recent results from API
|
47
|
+
recent_results = client.recent(limit: limit + offset)
|
48
|
+
|
49
|
+
# Apply offset
|
50
|
+
results = recent_results.drop(offset)
|
51
|
+
|
52
|
+
# Apply search filter if query provided
|
53
|
+
if query && !query.empty?
|
54
|
+
query_lower = query.downcase
|
55
|
+
results = results.select do |result|
|
56
|
+
(result.latex&.downcase&.include?(query_lower)) ||
|
57
|
+
(result.text&.downcase&.include?(query_lower))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Limit after filtering
|
62
|
+
results = results.take(limit)
|
63
|
+
|
64
|
+
# Format results
|
65
|
+
formatted_results = results.map do |result|
|
66
|
+
item = {
|
67
|
+
id: result.request_id,
|
68
|
+
created_at: result.timestamp,
|
69
|
+
confidence: result.confidence,
|
70
|
+
is_printed: result.printed?,
|
71
|
+
is_handwritten: result.handwritten?
|
72
|
+
}
|
73
|
+
|
74
|
+
# Include content if requested
|
75
|
+
if include_content
|
76
|
+
item[:latex] = result.latex
|
77
|
+
item[:text] = result.text
|
78
|
+
else
|
79
|
+
# Just include preview
|
80
|
+
item[:latex_preview] = truncate(result.latex, 100)
|
81
|
+
item[:text_preview] = truncate(result.text, 100)
|
82
|
+
end
|
83
|
+
|
84
|
+
item
|
85
|
+
end
|
86
|
+
|
87
|
+
# Format response
|
88
|
+
response_data = {
|
89
|
+
success: true,
|
90
|
+
query: query,
|
91
|
+
limit: limit,
|
92
|
+
offset: offset,
|
93
|
+
count: formatted_results.length,
|
94
|
+
results: formatted_results
|
95
|
+
}
|
96
|
+
|
97
|
+
json_response(response_data)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def self.truncate(text, max_length)
|
104
|
+
return nil unless text
|
105
|
+
return text if text.length <= max_length
|
106
|
+
"#{text[0...max_length]}..."
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|