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,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'mcp'
|
5
|
+
require 'mcp/transports/stdio' # Transport classes not auto-loaded
|
6
|
+
rescue LoadError
|
7
|
+
raise LoadError, <<~ERROR
|
8
|
+
The 'mcp' gem is required for MCP server functionality.
|
9
|
+
|
10
|
+
Add to your Gemfile:
|
11
|
+
gem 'mcp'
|
12
|
+
|
13
|
+
Or install directly:
|
14
|
+
gem install mcp
|
15
|
+
|
16
|
+
Official Ruby MCP SDK: https://github.com/modelcontextprotocol/ruby-sdk
|
17
|
+
ERROR
|
18
|
+
end
|
19
|
+
|
20
|
+
module Mathpix
|
21
|
+
module MCP
|
22
|
+
# MCP Server for Mathpix API
|
23
|
+
#
|
24
|
+
# Uses official Ruby MCP SDK (modelcontextprotocol/ruby-sdk)
|
25
|
+
# Provides 9 tools and 4 resources as thin delegates to core Mathpix::Client
|
26
|
+
#
|
27
|
+
# The geodesic path: official SDK, thin wrapper, thick core
|
28
|
+
#
|
29
|
+
# @example Start STDIO server
|
30
|
+
# require 'mathpix/mcp'
|
31
|
+
#
|
32
|
+
# Mathpix.configure do |config|
|
33
|
+
# config.app_id = ENV['MATHPIX_APP_ID']
|
34
|
+
# config.app_key = ENV['MATHPIX_APP_KEY']
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# Mathpix::MCP::Server.run
|
38
|
+
#
|
39
|
+
# @example With custom configuration
|
40
|
+
# server = Mathpix::MCP::Server.new(
|
41
|
+
# name: "mathpix-custom",
|
42
|
+
# version: "1.0.0",
|
43
|
+
# mathpix_client: custom_client
|
44
|
+
# )
|
45
|
+
# transport = server.create_stdio_transport
|
46
|
+
# transport.open
|
47
|
+
class Server
|
48
|
+
attr_reader :name, :version, :mathpix_client, :mcp_server
|
49
|
+
|
50
|
+
# Initialize MCP server
|
51
|
+
#
|
52
|
+
# @param name [String] server name
|
53
|
+
# @param version [String] server version
|
54
|
+
# @param mathpix_client [Mathpix::Client] optional client instance
|
55
|
+
def initialize(name: "mathpix", version: Mathpix::VERSION, mathpix_client: nil)
|
56
|
+
@name = name
|
57
|
+
@version = version
|
58
|
+
@mathpix_client = mathpix_client || Mathpix.client
|
59
|
+
@mcp_server = create_mcp_server
|
60
|
+
end
|
61
|
+
|
62
|
+
# Create STDIO transport (standard MCP transport)
|
63
|
+
#
|
64
|
+
# @return [::MCP::Transports::StdioTransport]
|
65
|
+
def create_stdio_transport
|
66
|
+
::MCP::Transports::StdioTransport.new(@mcp_server)
|
67
|
+
end
|
68
|
+
|
69
|
+
# Run MCP server with STDIO transport (blocking)
|
70
|
+
#
|
71
|
+
# Standard way to run MCP server via stdio
|
72
|
+
def run
|
73
|
+
transport = create_stdio_transport
|
74
|
+
transport.open
|
75
|
+
end
|
76
|
+
|
77
|
+
# Server capabilities
|
78
|
+
#
|
79
|
+
# @return [Hash] MCP server capabilities
|
80
|
+
def capabilities
|
81
|
+
{
|
82
|
+
tools: tool_classes.map(&:name),
|
83
|
+
resources: resource_specs.map { |r| r[:uri] }
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
# Class method: run server directly
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# Mathpix::MCP::Server.run
|
91
|
+
def self.run(**options)
|
92
|
+
new(**options).run
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# Create official MCP::Server with tools and resources
|
98
|
+
#
|
99
|
+
# Uses official Ruby MCP SDK structure
|
100
|
+
def create_mcp_server
|
101
|
+
::MCP::Server.new(
|
102
|
+
name: @name,
|
103
|
+
version: @version,
|
104
|
+
tools: tool_classes,
|
105
|
+
resources: build_resources,
|
106
|
+
server_context: { mathpix_client: @mathpix_client }
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
# List of all tool classes (using official MCP::Tool)
|
111
|
+
#
|
112
|
+
# @return [Array<Class>] tool classes
|
113
|
+
def tool_classes
|
114
|
+
[
|
115
|
+
Tools::ConvertImageTool,
|
116
|
+
Tools::ConvertDocumentTool,
|
117
|
+
Tools::ConvertStrokesTool,
|
118
|
+
Tools::BatchConvertTool,
|
119
|
+
Tools::CheckDocumentStatusTool,
|
120
|
+
Tools::SearchResultsTool,
|
121
|
+
Tools::GetUsageTool,
|
122
|
+
Tools::GetAccountInfoTool,
|
123
|
+
Tools::ListFormatsTool
|
124
|
+
]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Build MCP::Resource objects from specifications
|
128
|
+
#
|
129
|
+
# @return [Array<::MCP::Resource>] resource objects
|
130
|
+
def build_resources
|
131
|
+
resource_specs.map do |spec|
|
132
|
+
::MCP::Resource.new(
|
133
|
+
uri: spec[:uri],
|
134
|
+
name: spec[:name],
|
135
|
+
description: spec[:description],
|
136
|
+
mime_type: spec[:mime_type]
|
137
|
+
)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Resource specifications
|
142
|
+
#
|
143
|
+
# @return [Array<Hash>] resource specs
|
144
|
+
def resource_specs
|
145
|
+
[
|
146
|
+
{
|
147
|
+
uri: "mathpix://snip/recent/latest",
|
148
|
+
name: "latest-snip",
|
149
|
+
description: "Most recent capture result",
|
150
|
+
mime_type: "application/json"
|
151
|
+
},
|
152
|
+
{
|
153
|
+
uri: "mathpix://snip/stats",
|
154
|
+
name: "snip-stats",
|
155
|
+
description: "Overall capture statistics",
|
156
|
+
mime_type: "application/json"
|
157
|
+
},
|
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"
|
163
|
+
},
|
164
|
+
{
|
165
|
+
uri: "mathpix://formats/list",
|
166
|
+
name: "formats-list",
|
167
|
+
description: "Available output formats",
|
168
|
+
mime_type: "application/json"
|
169
|
+
}
|
170
|
+
]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_tool'
|
4
|
+
|
5
|
+
module Mathpix
|
6
|
+
module MCP
|
7
|
+
module Tools
|
8
|
+
# Batch Convert Tool
|
9
|
+
#
|
10
|
+
# Converts multiple images in batch for efficiency
|
11
|
+
# Thin delegate to Mathpix::Client#snap with batch processing
|
12
|
+
#
|
13
|
+
# The geodesic path: official SDK + batch iteration
|
14
|
+
class BatchConvertTool < BaseTool
|
15
|
+
description "Convert multiple images in batch using Mathpix OCR"
|
16
|
+
|
17
|
+
input_schema(
|
18
|
+
properties: {
|
19
|
+
image_paths: {
|
20
|
+
type: "array",
|
21
|
+
items: { type: "string" },
|
22
|
+
description: "Array of image paths or URLs to process"
|
23
|
+
},
|
24
|
+
formats: {
|
25
|
+
type: "array",
|
26
|
+
items: { type: "string" },
|
27
|
+
description: "Output formats for all images: latex, text, mathml, asciimath (default: latex_styled, text)"
|
28
|
+
},
|
29
|
+
parallel: {
|
30
|
+
type: "boolean",
|
31
|
+
description: "Process images in parallel (default: false)"
|
32
|
+
},
|
33
|
+
max_parallel: {
|
34
|
+
type: "number",
|
35
|
+
description: "Maximum number of parallel requests (default: 3)"
|
36
|
+
}
|
37
|
+
},
|
38
|
+
required: ["image_paths"]
|
39
|
+
)
|
40
|
+
|
41
|
+
def self.call(image_paths:, formats: nil, parallel: false, max_parallel: 3, server_context:)
|
42
|
+
safe_execute do
|
43
|
+
client = mathpix_client(server_context)
|
44
|
+
|
45
|
+
# Extract formats or use defaults
|
46
|
+
output_formats = extract_formats(formats, client)
|
47
|
+
|
48
|
+
# Normalize paths
|
49
|
+
normalized_paths = image_paths.map do |path|
|
50
|
+
url?(path) ? path : normalize_path(path)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Process images
|
54
|
+
results = if parallel
|
55
|
+
# TODO: Implement parallel processing (requires threading)
|
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
|
+
end
|
61
|
+
|
62
|
+
# Format response
|
63
|
+
response_data = {
|
64
|
+
success: true,
|
65
|
+
batch_size: image_paths.length,
|
66
|
+
formats: output_formats,
|
67
|
+
results: results,
|
68
|
+
summary: {
|
69
|
+
total: results.length,
|
70
|
+
successful: results.count { |r| r[:success] },
|
71
|
+
failed: results.count { |r| !r[:success] }
|
72
|
+
}
|
73
|
+
}
|
74
|
+
|
75
|
+
json_response(response_data)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def self.process_batch_sequential(client, paths, formats)
|
82
|
+
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
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_tool'
|
4
|
+
|
5
|
+
module Mathpix
|
6
|
+
module MCP
|
7
|
+
module Tools
|
8
|
+
# Check Document Status Tool
|
9
|
+
#
|
10
|
+
# Polls document conversion status for async operations
|
11
|
+
# Thin delegate to Mathpix::Client#get_document_status
|
12
|
+
#
|
13
|
+
# The geodesic path: official SDK + status polling
|
14
|
+
class CheckDocumentStatusTool < BaseTool
|
15
|
+
description "Check the status of a document conversion (PDF, DOCX, PPTX)"
|
16
|
+
|
17
|
+
input_schema(
|
18
|
+
properties: {
|
19
|
+
conversion_id: {
|
20
|
+
type: "string",
|
21
|
+
description: "Document conversion ID returned from convert_document"
|
22
|
+
}
|
23
|
+
},
|
24
|
+
required: ["conversion_id"]
|
25
|
+
)
|
26
|
+
|
27
|
+
def self.call(conversion_id:, server_context:)
|
28
|
+
safe_execute do
|
29
|
+
client = mathpix_client(server_context)
|
30
|
+
|
31
|
+
# Delegate to core gem
|
32
|
+
status_data = client.get_document_status(conversion_id)
|
33
|
+
|
34
|
+
# Format response
|
35
|
+
response_data = {
|
36
|
+
success: true,
|
37
|
+
conversion_id: conversion_id,
|
38
|
+
status: status_data['status'],
|
39
|
+
progress: status_data['percent_done'],
|
40
|
+
metadata: {}
|
41
|
+
}
|
42
|
+
|
43
|
+
# Add completion data if available
|
44
|
+
if status_data['status'] == 'completed'
|
45
|
+
response_data[:metadata][:pages] = status_data['num_pages']
|
46
|
+
response_data[:metadata][:processing_time] = status_data['processing_time']
|
47
|
+
response_data[:results] = {
|
48
|
+
markdown_url: status_data['markdown_url'],
|
49
|
+
latex_url: status_data['latex_url'],
|
50
|
+
html_url: status_data['html_url']
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Add error info if failed
|
55
|
+
if status_data['status'] == 'error' || status_data['status'] == 'failed'
|
56
|
+
response_data[:error] = status_data['error']
|
57
|
+
response_data[:error_info] = status_data['error_info']
|
58
|
+
end
|
59
|
+
|
60
|
+
json_response(response_data)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_tool'
|
4
|
+
|
5
|
+
module Mathpix
|
6
|
+
module MCP
|
7
|
+
module Tools
|
8
|
+
# Convert Document Tool
|
9
|
+
#
|
10
|
+
# Converts documents (PDF, DOCX, PPTX) to Markdown, LaTeX, or other formats
|
11
|
+
# Thin delegate to Mathpix::Document
|
12
|
+
#
|
13
|
+
# The geodesic path: official SDK + Document class delegation
|
14
|
+
class ConvertDocumentTool < BaseTool
|
15
|
+
description "Convert document (PDF, DOCX, PPTX) to Markdown, LaTeX, HTML, or other formats using Mathpix OCR"
|
16
|
+
|
17
|
+
input_schema(
|
18
|
+
properties: {
|
19
|
+
document_path: {
|
20
|
+
type: "string",
|
21
|
+
description: "Path to document file or URL (PDF, DOCX, or PPTX)"
|
22
|
+
},
|
23
|
+
formats: {
|
24
|
+
type: "array",
|
25
|
+
items: { type: "string" },
|
26
|
+
description: "Output formats: markdown, latex, html, docx (default: markdown)"
|
27
|
+
},
|
28
|
+
include_tables: {
|
29
|
+
type: "boolean",
|
30
|
+
description: "Include table extraction as HTML"
|
31
|
+
},
|
32
|
+
max_wait: {
|
33
|
+
type: "number",
|
34
|
+
description: "Maximum wait time in seconds for conversion (default: 600)"
|
35
|
+
},
|
36
|
+
poll_interval: {
|
37
|
+
type: "number",
|
38
|
+
description: "Polling interval in seconds (default: 3.0)"
|
39
|
+
}
|
40
|
+
},
|
41
|
+
required: ["document_path"]
|
42
|
+
)
|
43
|
+
|
44
|
+
def self.call(document_path:, formats: nil, include_tables: false, max_wait: 600, poll_interval: 3.0, server_context:)
|
45
|
+
safe_execute do
|
46
|
+
client = mathpix_client(server_context)
|
47
|
+
|
48
|
+
# Normalize path
|
49
|
+
document_path = normalize_path(document_path) unless url?(document_path)
|
50
|
+
|
51
|
+
# Extract formats or use defaults
|
52
|
+
output_formats = extract_formats(formats, client)
|
53
|
+
|
54
|
+
# Use Document class (new unified interface)
|
55
|
+
doc = Mathpix::Document.new(client, document_path)
|
56
|
+
doc.with_formats(*output_formats)
|
57
|
+
doc.with_tables if include_tables
|
58
|
+
|
59
|
+
# Start conversion
|
60
|
+
conversion = doc.convert
|
61
|
+
|
62
|
+
# Wait for completion
|
63
|
+
conversion.wait_until_complete(max_wait: max_wait, poll_interval: poll_interval)
|
64
|
+
result = conversion.result
|
65
|
+
|
66
|
+
# Format response
|
67
|
+
response_data = {
|
68
|
+
success: true,
|
69
|
+
document_path: document_path,
|
70
|
+
formats: output_formats,
|
71
|
+
conversion_id: conversion.conversion_id,
|
72
|
+
results: {
|
73
|
+
markdown: result.markdown,
|
74
|
+
latex: result.latex,
|
75
|
+
html: result.html
|
76
|
+
},
|
77
|
+
metadata: {
|
78
|
+
document_type: conversion.document_type,
|
79
|
+
pages: result.pages,
|
80
|
+
processing_time: result.processing_time
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
json_response(response_data)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_tool'
|
4
|
+
|
5
|
+
module Mathpix
|
6
|
+
module MCP
|
7
|
+
module Tools
|
8
|
+
# Convert Image Tool
|
9
|
+
#
|
10
|
+
# Converts images (PNG, JPG, etc.) to LaTeX, text, or other formats
|
11
|
+
# Thin delegate to Mathpix::Client#snap
|
12
|
+
#
|
13
|
+
# The geodesic path: official SDK + core gem delegation
|
14
|
+
class ConvertImageTool < BaseTool
|
15
|
+
description "Convert image (PNG, JPG, etc.) to LaTeX, text, or other formats using Mathpix OCR"
|
16
|
+
|
17
|
+
input_schema(
|
18
|
+
properties: {
|
19
|
+
image_path: {
|
20
|
+
type: "string",
|
21
|
+
description: "Path to image file or URL (http:// or https://)"
|
22
|
+
},
|
23
|
+
formats: {
|
24
|
+
type: "array",
|
25
|
+
items: { type: "string" },
|
26
|
+
description: "Output formats: latex, text, mathml, asciimath, latex_styled, text_display, data, html (default: latex_styled, text)"
|
27
|
+
},
|
28
|
+
include_line_data: {
|
29
|
+
type: "boolean",
|
30
|
+
description: "Include line-level bounding boxes in response"
|
31
|
+
},
|
32
|
+
include_word_data: {
|
33
|
+
type: "boolean",
|
34
|
+
description: "Include word-level bounding boxes in response"
|
35
|
+
},
|
36
|
+
confidence_threshold: {
|
37
|
+
type: "number",
|
38
|
+
description: "Minimum confidence threshold (0.0-1.0)"
|
39
|
+
}
|
40
|
+
},
|
41
|
+
required: ["image_path"]
|
42
|
+
)
|
43
|
+
|
44
|
+
def self.call(image_path:, formats: nil, include_line_data: false, include_word_data: false, confidence_threshold: nil, server_context:)
|
45
|
+
safe_execute do
|
46
|
+
client = mathpix_client(server_context)
|
47
|
+
|
48
|
+
# Normalize path (expand ~, resolve relative paths)
|
49
|
+
image_path = normalize_path(image_path) unless url?(image_path)
|
50
|
+
|
51
|
+
# Extract formats or use defaults
|
52
|
+
output_formats = extract_formats(formats, client)
|
53
|
+
|
54
|
+
# Build options
|
55
|
+
options = {}
|
56
|
+
options[:formats] = output_formats
|
57
|
+
options[:include_line_data] = true if include_line_data
|
58
|
+
options[:include_word_data] = true if include_word_data
|
59
|
+
options[:confidence_threshold] = confidence_threshold if confidence_threshold
|
60
|
+
|
61
|
+
# Delegate to core gem
|
62
|
+
result = client.snap(image_path, **options)
|
63
|
+
|
64
|
+
# Format response
|
65
|
+
response_data = {
|
66
|
+
success: true,
|
67
|
+
image_path: image_path,
|
68
|
+
formats: output_formats,
|
69
|
+
results: {
|
70
|
+
latex: result.latex,
|
71
|
+
text: result.text,
|
72
|
+
confidence: result.confidence,
|
73
|
+
is_printed: result.printed?,
|
74
|
+
is_handwritten: result.handwritten?,
|
75
|
+
position: result.position
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
# Add optional data
|
80
|
+
response_data[:line_data] = result.line_data if include_line_data
|
81
|
+
response_data[:word_data] = result.word_data if include_word_data
|
82
|
+
response_data[:results][:mathml] = result.mathml if result.mathml
|
83
|
+
response_data[:results][:asciimath] = result.asciimath if result.asciimath
|
84
|
+
|
85
|
+
json_response(response_data)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_tool'
|
4
|
+
|
5
|
+
module Mathpix
|
6
|
+
module MCP
|
7
|
+
module Tools
|
8
|
+
# Convert Strokes Tool
|
9
|
+
#
|
10
|
+
# Converts handwritten strokes to LaTeX, text, or other formats
|
11
|
+
# Thin delegate to Mathpix::Client#snap with strokes format
|
12
|
+
#
|
13
|
+
# The geodesic path: official SDK + stroke recognition
|
14
|
+
class ConvertStrokesTool < BaseTool
|
15
|
+
description "Convert handwritten strokes to LaTeX, text, or other formats using Mathpix OCR"
|
16
|
+
|
17
|
+
input_schema(
|
18
|
+
properties: {
|
19
|
+
strokes: {
|
20
|
+
type: "array",
|
21
|
+
description: "Array of stroke arrays, where each stroke is an array of [x, y] coordinates"
|
22
|
+
},
|
23
|
+
formats: {
|
24
|
+
type: "array",
|
25
|
+
items: { type: "string" },
|
26
|
+
description: "Output formats: latex, text, mathml, asciimath (default: latex_styled, text)"
|
27
|
+
},
|
28
|
+
width: {
|
29
|
+
type: "number",
|
30
|
+
description: "Canvas width for stroke normalization"
|
31
|
+
},
|
32
|
+
height: {
|
33
|
+
type: "number",
|
34
|
+
description: "Canvas height for stroke normalization"
|
35
|
+
}
|
36
|
+
},
|
37
|
+
required: ["strokes"]
|
38
|
+
)
|
39
|
+
|
40
|
+
def self.call(strokes:, formats: nil, width: nil, height: nil, server_context:)
|
41
|
+
safe_execute do
|
42
|
+
client = mathpix_client(server_context)
|
43
|
+
|
44
|
+
# Extract formats or use defaults
|
45
|
+
output_formats = extract_formats(formats, client)
|
46
|
+
|
47
|
+
# Build strokes data structure
|
48
|
+
strokes_data = {
|
49
|
+
strokes: strokes
|
50
|
+
}
|
51
|
+
strokes_data[:width] = width if width
|
52
|
+
strokes_data[:height] = height if height
|
53
|
+
|
54
|
+
# Delegate to core gem snap method with strokes
|
55
|
+
result = client.snap(strokes_data, formats: output_formats)
|
56
|
+
|
57
|
+
# Format response
|
58
|
+
response_data = {
|
59
|
+
success: true,
|
60
|
+
input_type: "strokes",
|
61
|
+
stroke_count: strokes.length,
|
62
|
+
formats: output_formats,
|
63
|
+
results: {
|
64
|
+
latex: result.latex,
|
65
|
+
text: result.text,
|
66
|
+
confidence: result.confidence,
|
67
|
+
is_handwritten: result.handwritten?,
|
68
|
+
position: result.position
|
69
|
+
}
|
70
|
+
}
|
71
|
+
|
72
|
+
# Add optional formats
|
73
|
+
response_data[:results][:mathml] = result.mathml if result.mathml
|
74
|
+
response_data[:results][:asciimath] = result.asciimath if result.asciimath
|
75
|
+
|
76
|
+
json_response(response_data)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_tool'
|
4
|
+
|
5
|
+
module Mathpix
|
6
|
+
module MCP
|
7
|
+
module Tools
|
8
|
+
# Get Account Info Tool
|
9
|
+
#
|
10
|
+
# Retrieves account information and plan details
|
11
|
+
# Thin delegate to Mathpix::Client (account endpoint)
|
12
|
+
#
|
13
|
+
# The geodesic path: official SDK + account management
|
14
|
+
class GetAccountInfoTool < BaseTool
|
15
|
+
description "Get Mathpix account information, plan details, and limits"
|
16
|
+
|
17
|
+
input_schema(
|
18
|
+
properties: {},
|
19
|
+
required: []
|
20
|
+
)
|
21
|
+
|
22
|
+
def self.call(server_context:)
|
23
|
+
safe_execute do
|
24
|
+
client = mathpix_client(server_context)
|
25
|
+
|
26
|
+
begin
|
27
|
+
# Try to call the account endpoint
|
28
|
+
account_data = client.get('/account')
|
29
|
+
|
30
|
+
response_data = {
|
31
|
+
success: true,
|
32
|
+
account: {
|
33
|
+
email: account_data['email'],
|
34
|
+
plan: account_data['plan'],
|
35
|
+
created_at: account_data['created_at'],
|
36
|
+
limits: {
|
37
|
+
monthly_requests: account_data['monthly_requests'],
|
38
|
+
max_file_size: account_data['max_file_size'],
|
39
|
+
features: account_data['features']
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
json_response(response_data)
|
45
|
+
rescue Mathpix::APIError => e
|
46
|
+
# Handle API-specific errors properly
|
47
|
+
error_response(e)
|
48
|
+
rescue StandardError => e
|
49
|
+
# Handle unexpected errors
|
50
|
+
error_response(e)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|