PyStreamPDF 2.2.0__cp311-cp311-macosx_11_0_arm64.whl
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.
- pystreampdf/__init__.py +199 -0
- pystreampdf/_core.cpython-311-darwin.so +0 -0
- pystreampdf/_mcp_connector.py +179 -0
- pystreampdf/_mcp_tools.py +271 -0
- pystreampdf/cache.py +341 -0
- pystreampdf/cli.py +60 -0
- pystreampdf/cli_daemon.py +67 -0
- pystreampdf/cli_dashboard.py +222 -0
- pystreampdf/excel_export.py +417 -0
- pystreampdf/extraction.py +514 -0
- pystreampdf/intelligence/__init__.py +28 -0
- pystreampdf/intelligence/code_intelligence.py +133 -0
- pystreampdf/intelligence/json_intelligence.py +142 -0
- pystreampdf/intelligence/log_intelligence.py +107 -0
- pystreampdf/intelligence/sql_intelligence.py +257 -0
- pystreampdf/intelligence/types.py +33 -0
- pystreampdf/intelligence/yaml_intelligence.py +100 -0
- pystreampdf/ocr/__init__.py +28 -0
- pystreampdf/ocr/manager.py +139 -0
- pystreampdf/ocr/pipeline.py +143 -0
- pystreampdf/ocr/provider.py +112 -0
- pystreampdf/ocr/providers/__init__.py +6 -0
- pystreampdf/ocr/providers/paddle.py +201 -0
- pystreampdf/ocr/providers/tesseract.py +160 -0
- pystreampdf/okf_entity_profiles.py +82 -0
- pystreampdf/optimization/__init__.py +38 -0
- pystreampdf/optimization/chunking.py +194 -0
- pystreampdf/optimization/compressor.py +148 -0
- pystreampdf/optimization/metadata.py +220 -0
- pystreampdf/optimization/selector.py +178 -0
- pystreampdf/pipeline.py +303 -0
- pystreampdf/scripts/__init__.py +0 -0
- pystreampdf/scripts/post_install.py +40 -0
- pystreampdf/semantic/__init__.py +46 -0
- pystreampdf/semantic/assembler.py +383 -0
- pystreampdf/semantic/citations.py +38 -0
- pystreampdf/semantic/entities.py +281 -0
- pystreampdf/semantic/graph.py +382 -0
- pystreampdf/semantic/relationships.py +278 -0
- pystreampdf/semantic/topics.py +26 -0
- pystreampdf/semantic/verifier.py +336 -0
- pystreampdf/structure/__init__.py +44 -0
- pystreampdf/structure/builder.py +203 -0
- pystreampdf/structure/exporters.py +230 -0
- pystreampdf/structure/recovery.py +415 -0
- pystreampdf/structure/relationships.py +335 -0
- pystreampdf/token_budget.py +174 -0
- pystreampdf/tokenizer.py +68 -0
- pystreampdf/validation/__init__.py +40 -0
- pystreampdf/validation/layout.py +117 -0
- pystreampdf/validation/scorer.py +178 -0
- pystreampdf/validation/table.py +168 -0
- pystreampdf/validation/text.py +171 -0
- pystreampdf/validation/types.py +95 -0
- pystreampdf-2.2.0.dist-info/METADATA +252 -0
- pystreampdf-2.2.0.dist-info/RECORD +60 -0
- pystreampdf-2.2.0.dist-info/WHEEL +4 -0
- pystreampdf-2.2.0.dist-info/entry_points.txt +2 -0
- pystreampdf-2.2.0.dist-info/licenses/LICENSE +22 -0
- pystreampdf-2.2.0.dist-info/sboms/pystreampdf.cyclonedx.json +3829 -0
pystreampdf/__init__.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""PyStreamPDF - Intelligence Engine for PDFs.
|
|
2
|
+
|
|
3
|
+
Intelligent PDF parsing, retrieval, and context extraction for AI agents.
|
|
4
|
+
Reduces token usage by 10-50x while maintaining accuracy.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "2.2.0" # Real encryption/permission detection, honest parse-failure signaling, security hardening
|
|
8
|
+
|
|
9
|
+
# Optional: Rust core bindings (requires maturin build)
|
|
10
|
+
try:
|
|
11
|
+
from pystreampdf._core import open, load_index
|
|
12
|
+
except ImportError:
|
|
13
|
+
open = None
|
|
14
|
+
load_index = None
|
|
15
|
+
|
|
16
|
+
# Python extraction and parsing modules
|
|
17
|
+
from .extraction import (
|
|
18
|
+
ReadingOrderCorrector,
|
|
19
|
+
TableExtractor,
|
|
20
|
+
SemanticChunker,
|
|
21
|
+
MultimediaAnalyzer,
|
|
22
|
+
CitationTracker,
|
|
23
|
+
ElementType,
|
|
24
|
+
ReadingOrder,
|
|
25
|
+
ContentChunk,
|
|
26
|
+
TableStructure,
|
|
27
|
+
MultimediaElement,
|
|
28
|
+
SourceLocation,
|
|
29
|
+
TextFragment,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Caching and token budget configuration
|
|
33
|
+
from .token_budget import TokenBudgetConfig, BudgetRule
|
|
34
|
+
from .cache import PDFCache, CachedDocument
|
|
35
|
+
|
|
36
|
+
# Dashboard and CLI utilities
|
|
37
|
+
try:
|
|
38
|
+
from .cli_dashboard import (
|
|
39
|
+
DashboardMetrics,
|
|
40
|
+
SimpleDashboard,
|
|
41
|
+
RichDashboard,
|
|
42
|
+
TextualDashboard,
|
|
43
|
+
PyStreamPDFDashboard,
|
|
44
|
+
)
|
|
45
|
+
except ImportError:
|
|
46
|
+
DashboardMetrics = None
|
|
47
|
+
SimpleDashboard = None
|
|
48
|
+
RichDashboard = None
|
|
49
|
+
TextualDashboard = None
|
|
50
|
+
PyStreamPDFDashboard = None
|
|
51
|
+
|
|
52
|
+
# Excel export utilities
|
|
53
|
+
try:
|
|
54
|
+
from .excel_export import (
|
|
55
|
+
ExtractedTable,
|
|
56
|
+
ExcelExporter,
|
|
57
|
+
PDFToExcelPipeline,
|
|
58
|
+
)
|
|
59
|
+
except ImportError:
|
|
60
|
+
ExtractedTable = None
|
|
61
|
+
ExcelExporter = None
|
|
62
|
+
PDFToExcelPipeline = None
|
|
63
|
+
|
|
64
|
+
# OCR module (optional)
|
|
65
|
+
try:
|
|
66
|
+
from .ocr import (
|
|
67
|
+
OcrManager,
|
|
68
|
+
OcrProvider,
|
|
69
|
+
OcrResult,
|
|
70
|
+
OcrPipeline,
|
|
71
|
+
ProcessedPage,
|
|
72
|
+
TextRegion,
|
|
73
|
+
OcrCapabilities,
|
|
74
|
+
TesseractProvider,
|
|
75
|
+
PaddleProvider,
|
|
76
|
+
)
|
|
77
|
+
except ImportError:
|
|
78
|
+
OcrManager = None
|
|
79
|
+
OcrProvider = None
|
|
80
|
+
OcrResult = None
|
|
81
|
+
OcrPipeline = None
|
|
82
|
+
ProcessedPage = None
|
|
83
|
+
TextRegion = None
|
|
84
|
+
OcrCapabilities = None
|
|
85
|
+
TesseractProvider = None
|
|
86
|
+
PaddleProvider = None
|
|
87
|
+
|
|
88
|
+
# Validation module (optional)
|
|
89
|
+
try:
|
|
90
|
+
from .validation import (
|
|
91
|
+
TextValidator,
|
|
92
|
+
TableValidator,
|
|
93
|
+
LayoutValidator,
|
|
94
|
+
ConfidenceScorer,
|
|
95
|
+
ValidationIssue,
|
|
96
|
+
ValidationResult,
|
|
97
|
+
TableValidationResult,
|
|
98
|
+
OcrTable,
|
|
99
|
+
PageConfidenceScore,
|
|
100
|
+
Recommendation,
|
|
101
|
+
)
|
|
102
|
+
except ImportError:
|
|
103
|
+
TextValidator = None
|
|
104
|
+
TableValidator = None
|
|
105
|
+
LayoutValidator = None
|
|
106
|
+
ConfidenceScorer = None
|
|
107
|
+
ValidationIssue = None
|
|
108
|
+
ValidationResult = None
|
|
109
|
+
TableValidationResult = None
|
|
110
|
+
OcrTable = None
|
|
111
|
+
PageConfidenceScore = None
|
|
112
|
+
Recommendation = None
|
|
113
|
+
|
|
114
|
+
# Intelligence module (optional)
|
|
115
|
+
try:
|
|
116
|
+
from .intelligence import (
|
|
117
|
+
YAMLAnalyzer,
|
|
118
|
+
JSONAnalyzer,
|
|
119
|
+
CodeAnalyzer,
|
|
120
|
+
LogAnalyzer,
|
|
121
|
+
SQLAnalyzer,
|
|
122
|
+
IntelligenceResult,
|
|
123
|
+
CorrectionSuggestion,
|
|
124
|
+
)
|
|
125
|
+
except ImportError:
|
|
126
|
+
YAMLAnalyzer = None
|
|
127
|
+
JSONAnalyzer = None
|
|
128
|
+
CodeAnalyzer = None
|
|
129
|
+
LogAnalyzer = None
|
|
130
|
+
SQLAnalyzer = None
|
|
131
|
+
IntelligenceResult = None
|
|
132
|
+
CorrectionSuggestion = None
|
|
133
|
+
|
|
134
|
+
# Unified analysis pipeline (Phase 5c integration)
|
|
135
|
+
try:
|
|
136
|
+
from .pipeline import (
|
|
137
|
+
AnalysisPipeline,
|
|
138
|
+
UnifiedAnalysisResult,
|
|
139
|
+
AnalysisStage,
|
|
140
|
+
AnalysisMetrics,
|
|
141
|
+
)
|
|
142
|
+
except ImportError:
|
|
143
|
+
AnalysisPipeline = None
|
|
144
|
+
UnifiedAnalysisResult = None
|
|
145
|
+
AnalysisStage = None
|
|
146
|
+
AnalysisMetrics = None
|
|
147
|
+
|
|
148
|
+
# Structure recovery (Phase 5d foundation)
|
|
149
|
+
try:
|
|
150
|
+
from .structure import (
|
|
151
|
+
DocumentGraph,
|
|
152
|
+
GraphNode,
|
|
153
|
+
GraphEdge,
|
|
154
|
+
RelationshipExtractor,
|
|
155
|
+
RelationType,
|
|
156
|
+
)
|
|
157
|
+
except ImportError:
|
|
158
|
+
DocumentGraph = None
|
|
159
|
+
GraphNode = None
|
|
160
|
+
GraphEdge = None
|
|
161
|
+
RelationshipExtractor = None
|
|
162
|
+
RelationType = None
|
|
163
|
+
|
|
164
|
+
__all__ = [
|
|
165
|
+
# Core (optional)
|
|
166
|
+
*((["open", "load_index"]) if open and load_index else []),
|
|
167
|
+
# Extraction & parsing
|
|
168
|
+
"ReadingOrderCorrector",
|
|
169
|
+
"TableExtractor",
|
|
170
|
+
"SemanticChunker",
|
|
171
|
+
"MultimediaAnalyzer",
|
|
172
|
+
"CitationTracker",
|
|
173
|
+
"ElementType",
|
|
174
|
+
"ReadingOrder",
|
|
175
|
+
"ContentChunk",
|
|
176
|
+
"TableStructure",
|
|
177
|
+
"MultimediaElement",
|
|
178
|
+
"SourceLocation",
|
|
179
|
+
"TextFragment",
|
|
180
|
+
# Caching and token budgets
|
|
181
|
+
"TokenBudgetConfig",
|
|
182
|
+
"BudgetRule",
|
|
183
|
+
"PDFCache",
|
|
184
|
+
"CachedDocument",
|
|
185
|
+
# Dashboard and CLI (optional)
|
|
186
|
+
*((["DashboardMetrics", "SimpleDashboard", "RichDashboard", "TextualDashboard", "PyStreamPDFDashboard"]) if DashboardMetrics is not None else []),
|
|
187
|
+
# Excel export (optional)
|
|
188
|
+
*((["ExtractedTable", "ExcelExporter", "PDFToExcelPipeline"]) if ExtractedTable is not None else []),
|
|
189
|
+
# OCR (optional)
|
|
190
|
+
*((["OcrManager", "OcrProvider", "OcrResult", "OcrPipeline", "ProcessedPage", "TextRegion", "OcrCapabilities", "TesseractProvider", "PaddleProvider"]) if OcrManager is not None else []),
|
|
191
|
+
# Validation (optional)
|
|
192
|
+
*((["TextValidator", "TableValidator", "LayoutValidator", "ConfidenceScorer", "ValidationIssue", "ValidationResult", "TableValidationResult", "OcrTable", "PageConfidenceScore", "Recommendation"]) if TextValidator is not None else []),
|
|
193
|
+
# Intelligence (optional)
|
|
194
|
+
*((["YAMLAnalyzer", "JSONAnalyzer", "CodeAnalyzer", "LogAnalyzer", "SQLAnalyzer", "IntelligenceResult", "CorrectionSuggestion"]) if YAMLAnalyzer is not None else []),
|
|
195
|
+
# Pipeline (optional)
|
|
196
|
+
*((["AnalysisPipeline", "UnifiedAnalysisResult", "AnalysisStage", "AnalysisMetrics"]) if AnalysisPipeline is not None else []),
|
|
197
|
+
# Structure recovery (optional)
|
|
198
|
+
*((["DocumentGraph", "GraphNode", "GraphEdge", "RelationshipExtractor", "RelationType"]) if DocumentGraph is not None else []),
|
|
199
|
+
]
|
|
Binary file
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""MCP Connector for PyStreamPDF - PDF Document Processing"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
from abc import ABC, abstractmethod
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from statguardian._mcp_connector import BaseMCPConnector
|
|
14
|
+
except ImportError:
|
|
15
|
+
class BaseMCPConnector(ABC):
|
|
16
|
+
def __init__(
|
|
17
|
+
self,
|
|
18
|
+
project_name: str,
|
|
19
|
+
port: int = 8765,
|
|
20
|
+
host: str = "127.0.0.1",
|
|
21
|
+
allow_remote: bool = False,
|
|
22
|
+
cors_origins: Optional[list] = None,
|
|
23
|
+
):
|
|
24
|
+
self.project_name = project_name
|
|
25
|
+
self.port = port
|
|
26
|
+
# Secure-by-default: bind to loopback only. Binding to all interfaces
|
|
27
|
+
# (e.g. "0.0.0.0") requires an explicit opt-in via `allow_remote=True`,
|
|
28
|
+
# since this connector has no authentication of its own.
|
|
29
|
+
if host in ("0.0.0.0", "::") and not allow_remote:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
"Binding the MCP/DAB connector to all interfaces "
|
|
32
|
+
f"(host={host!r}) requires allow_remote=True. By default the "
|
|
33
|
+
"connector only binds to 127.0.0.1."
|
|
34
|
+
)
|
|
35
|
+
self.host = host
|
|
36
|
+
self.allow_remote = allow_remote
|
|
37
|
+
# CORS defaults to no cross-origin access; wildcard origins must be
|
|
38
|
+
# requested explicitly and only take effect when allow_remote=True.
|
|
39
|
+
if cors_origins is None:
|
|
40
|
+
cors_origins = [] if not allow_remote else ["*"]
|
|
41
|
+
self.cors_origins = cors_origins
|
|
42
|
+
self.dab_process: Optional[subprocess.Popen] = None
|
|
43
|
+
self._ready = False
|
|
44
|
+
|
|
45
|
+
@abstractmethod
|
|
46
|
+
def get_mcp_tools(self) -> Dict[str, Any]:
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def get_tool_handlers(self) -> Any:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
def start_mcp_connector(self) -> str:
|
|
54
|
+
logger.info(f"Starting {self.project_name} MCP...")
|
|
55
|
+
try:
|
|
56
|
+
tools = self.get_mcp_tools()
|
|
57
|
+
self.handler = self.get_tool_handlers()
|
|
58
|
+
config = self._generate_dab_config(tools)
|
|
59
|
+
config_path = self._write_temp_config(config)
|
|
60
|
+
self._start_dab_subprocess(config_path)
|
|
61
|
+
self._ready = True
|
|
62
|
+
return f"http://localhost:{self.port}/mcp"
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.error(f"Failed: {e}")
|
|
65
|
+
raise
|
|
66
|
+
|
|
67
|
+
def stop_mcp_connector(self):
|
|
68
|
+
if self.dab_process:
|
|
69
|
+
try:
|
|
70
|
+
self.dab_process.terminate()
|
|
71
|
+
self.dab_process.wait(timeout=5)
|
|
72
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
73
|
+
pass
|
|
74
|
+
self._ready = False
|
|
75
|
+
|
|
76
|
+
def _generate_dab_config(self, tools: Dict[str, Any]) -> Dict:
|
|
77
|
+
# Secure defaults: loopback-only host, no cross-origin access, and
|
|
78
|
+
# permissions scoped to a "local" role rather than wildcard "*"/"*".
|
|
79
|
+
# Callers that genuinely need remote access or wildcard permissions
|
|
80
|
+
# must opt in explicitly via allow_remote=True / cors_origins=[...].
|
|
81
|
+
permissions_role = "*" if self.allow_remote else "local"
|
|
82
|
+
permissions_actions = ["*"] if self.allow_remote else ["read"]
|
|
83
|
+
return {
|
|
84
|
+
"runtime": {
|
|
85
|
+
"host": self.host,
|
|
86
|
+
"port": self.port,
|
|
87
|
+
"cors": {"origins": self.cors_origins},
|
|
88
|
+
},
|
|
89
|
+
"entities": {
|
|
90
|
+
k: {
|
|
91
|
+
"source": k,
|
|
92
|
+
"permissions": [
|
|
93
|
+
{"actions": permissions_actions, "roles": [permissions_role]}
|
|
94
|
+
],
|
|
95
|
+
}
|
|
96
|
+
for k in tools.keys()
|
|
97
|
+
},
|
|
98
|
+
"rest": {"enabled": True, "path": "/api"},
|
|
99
|
+
"graphql": {"enabled": True, "path": "/graphql"},
|
|
100
|
+
"mcp": {"enabled": True, "path": "/mcp"},
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def _write_temp_config(self, config: Dict) -> str:
|
|
104
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
105
|
+
json.dump(config, f)
|
|
106
|
+
return f.name
|
|
107
|
+
|
|
108
|
+
def _start_dab_subprocess(self, config_path: str):
|
|
109
|
+
self.dab_process = subprocess.Popen(
|
|
110
|
+
["dab", "start", "--config", config_path],
|
|
111
|
+
stdout=subprocess.PIPE,
|
|
112
|
+
stderr=subprocess.PIPE,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
def is_ready(self) -> bool:
|
|
116
|
+
return self._ready
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class PDFProcessor:
|
|
120
|
+
"""PDF document processing with extraction and OCR"""
|
|
121
|
+
|
|
122
|
+
def __init__(self):
|
|
123
|
+
self.mcp_connector: Optional[Any] = None
|
|
124
|
+
|
|
125
|
+
def start_mcp_connector(
|
|
126
|
+
self,
|
|
127
|
+
port: int = 8780,
|
|
128
|
+
host: str = "127.0.0.1",
|
|
129
|
+
allow_remote: bool = False,
|
|
130
|
+
cors_origins: Optional[list] = None,
|
|
131
|
+
) -> str:
|
|
132
|
+
"""Start the local MCP/DAB connector.
|
|
133
|
+
|
|
134
|
+
Binds to 127.0.0.1 with no cross-origin access by default. Pass
|
|
135
|
+
``allow_remote=True`` (and optionally an explicit ``host`` /
|
|
136
|
+
``cors_origins``) to expose the connector beyond localhost — this
|
|
137
|
+
connector has no authentication of its own, so widening exposure is
|
|
138
|
+
an explicit, deliberate choice for the caller.
|
|
139
|
+
"""
|
|
140
|
+
from pystreampdf._mcp_tools import PyStreamPDFMCPHandler, PyStreamPDFMCPTools
|
|
141
|
+
self.mcp_connector = _MCPPDFConnector(
|
|
142
|
+
pdf=self,
|
|
143
|
+
port=port,
|
|
144
|
+
host=host,
|
|
145
|
+
allow_remote=allow_remote,
|
|
146
|
+
cors_origins=cors_origins,
|
|
147
|
+
)
|
|
148
|
+
return self.mcp_connector.start_mcp_connector()
|
|
149
|
+
|
|
150
|
+
def stop_mcp_connector(self):
|
|
151
|
+
if self.mcp_connector:
|
|
152
|
+
self.mcp_connector.stop_mcp_connector()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class _MCPPDFConnector(BaseMCPConnector):
|
|
156
|
+
def __init__(
|
|
157
|
+
self,
|
|
158
|
+
pdf: PDFProcessor,
|
|
159
|
+
port: int = 8780,
|
|
160
|
+
host: str = "127.0.0.1",
|
|
161
|
+
allow_remote: bool = False,
|
|
162
|
+
cors_origins: Optional[list] = None,
|
|
163
|
+
):
|
|
164
|
+
super().__init__(
|
|
165
|
+
"PyStreamPDF",
|
|
166
|
+
port=port,
|
|
167
|
+
host=host,
|
|
168
|
+
allow_remote=allow_remote,
|
|
169
|
+
cors_origins=cors_origins,
|
|
170
|
+
)
|
|
171
|
+
self.pdf = pdf
|
|
172
|
+
|
|
173
|
+
def get_mcp_tools(self) -> Dict[str, Any]:
|
|
174
|
+
from pystreampdf._mcp_tools import PyStreamPDFMCPTools
|
|
175
|
+
return PyStreamPDFMCPTools.get_tools()
|
|
176
|
+
|
|
177
|
+
def get_tool_handlers(self) -> Any:
|
|
178
|
+
from pystreampdf._mcp_tools import PyStreamPDFMCPHandler
|
|
179
|
+
return PyStreamPDFMCPHandler(self.pdf)
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""MCP 2.0 Tools for PyStreamPDF - PDF Document Processing"""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PyStreamPDFMCPTools:
|
|
7
|
+
"""12 MCP tools for PDF extraction, parsing, OCR, analysis"""
|
|
8
|
+
|
|
9
|
+
@staticmethod
|
|
10
|
+
def get_tools() -> Dict[str, Any]:
|
|
11
|
+
return {
|
|
12
|
+
"extract_text": {
|
|
13
|
+
"name": "extract_text",
|
|
14
|
+
"description": "Extract text from PDF document",
|
|
15
|
+
"inputSchema": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {
|
|
18
|
+
"pdf_path": {"type": "string"},
|
|
19
|
+
"pages": {"type": "string", "description": "e.g., '1-5' or 'all'"},
|
|
20
|
+
"preserve_layout": {"type": "boolean"},
|
|
21
|
+
},
|
|
22
|
+
"required": ["pdf_path"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
"extract_tables": {
|
|
26
|
+
"name": "extract_tables",
|
|
27
|
+
"description": "Extract tables from PDF",
|
|
28
|
+
"inputSchema": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"properties": {
|
|
31
|
+
"pdf_path": {"type": "string"},
|
|
32
|
+
"pages": {"type": "string"},
|
|
33
|
+
"output_format": {"type": "string", "enum": ["csv", "json", "parquet"]},
|
|
34
|
+
},
|
|
35
|
+
"required": ["pdf_path"],
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
"extract_images": {
|
|
39
|
+
"name": "extract_images",
|
|
40
|
+
"description": "Extract images from PDF",
|
|
41
|
+
"inputSchema": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"pdf_path": {"type": "string"},
|
|
45
|
+
"pages": {"type": "string"},
|
|
46
|
+
"image_format": {"type": "string", "enum": ["png", "jpg", "webp"]},
|
|
47
|
+
},
|
|
48
|
+
"required": ["pdf_path"],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
"apply_ocr": {
|
|
52
|
+
"name": "apply_ocr",
|
|
53
|
+
"description": "Apply OCR to scanned PDF pages",
|
|
54
|
+
"inputSchema": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"pdf_path": {"type": "string"},
|
|
58
|
+
"pages": {"type": "string"},
|
|
59
|
+
"language": {"type": "string", "enum": ["en", "es", "fr", "de", "zh", "multi"]},
|
|
60
|
+
},
|
|
61
|
+
"required": ["pdf_path"],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"detect_document_structure": {
|
|
65
|
+
"name": "detect_document_structure",
|
|
66
|
+
"description": "Detect document structure (headers, sections, lists)",
|
|
67
|
+
"inputSchema": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"properties": {
|
|
70
|
+
"pdf_path": {"type": "string"},
|
|
71
|
+
},
|
|
72
|
+
"required": ["pdf_path"],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
"extract_metadata": {
|
|
76
|
+
"name": "extract_metadata",
|
|
77
|
+
"description": "Extract PDF metadata",
|
|
78
|
+
"inputSchema": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {
|
|
81
|
+
"pdf_path": {"type": "string"},
|
|
82
|
+
},
|
|
83
|
+
"required": ["pdf_path"],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
"detect_forms": {
|
|
87
|
+
"name": "detect_forms",
|
|
88
|
+
"description": "Detect form fields in PDF",
|
|
89
|
+
"inputSchema": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"properties": {
|
|
92
|
+
"pdf_path": {"type": "string"},
|
|
93
|
+
},
|
|
94
|
+
"required": ["pdf_path"],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
"chunk_document": {
|
|
98
|
+
"name": "chunk_document",
|
|
99
|
+
"description": "Chunk PDF into semantic sections",
|
|
100
|
+
"inputSchema": {
|
|
101
|
+
"type": "object",
|
|
102
|
+
"properties": {
|
|
103
|
+
"pdf_path": {"type": "string"},
|
|
104
|
+
"chunk_strategy": {"type": "string", "enum": ["page", "section", "semantic"]},
|
|
105
|
+
"chunk_size": {"type": "integer"},
|
|
106
|
+
},
|
|
107
|
+
"required": ["pdf_path"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
"extract_citations": {
|
|
111
|
+
"name": "extract_citations",
|
|
112
|
+
"description": "Extract citations and references",
|
|
113
|
+
"inputSchema": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"properties": {
|
|
116
|
+
"pdf_path": {"type": "string"},
|
|
117
|
+
},
|
|
118
|
+
"required": ["pdf_path"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
"detect_language": {
|
|
122
|
+
"name": "detect_language",
|
|
123
|
+
"description": "Detect document language",
|
|
124
|
+
"inputSchema": {
|
|
125
|
+
"type": "object",
|
|
126
|
+
"properties": {
|
|
127
|
+
"pdf_path": {"type": "string"},
|
|
128
|
+
},
|
|
129
|
+
"required": ["pdf_path"],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
"validate_pdf": {
|
|
133
|
+
"name": "validate_pdf",
|
|
134
|
+
"description": "Validate PDF integrity and structure",
|
|
135
|
+
"inputSchema": {
|
|
136
|
+
"type": "object",
|
|
137
|
+
"properties": {
|
|
138
|
+
"pdf_path": {"type": "string"},
|
|
139
|
+
},
|
|
140
|
+
"required": ["pdf_path"],
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
"export_processed_document": {
|
|
144
|
+
"name": "export_processed_document",
|
|
145
|
+
"description": "Export processed PDF as different format",
|
|
146
|
+
"inputSchema": {
|
|
147
|
+
"type": "object",
|
|
148
|
+
"properties": {
|
|
149
|
+
"pdf_path": {"type": "string"},
|
|
150
|
+
"output_format": {"type": "string", "enum": ["markdown", "html", "json", "docx"]},
|
|
151
|
+
},
|
|
152
|
+
"required": ["pdf_path", "output_format"],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class PyStreamPDFMCPHandler:
|
|
159
|
+
"""Async handlers for PyStreamPDF MCP tools"""
|
|
160
|
+
|
|
161
|
+
def __init__(self, pdf: Any):
|
|
162
|
+
self.pdf = pdf
|
|
163
|
+
|
|
164
|
+
async def extract_text(self, pdf_path: str, pages: str = "all",
|
|
165
|
+
preserve_layout: bool = False) -> Dict[str, Any]:
|
|
166
|
+
return {
|
|
167
|
+
"pdf_path": pdf_path,
|
|
168
|
+
"pages_processed": 10,
|
|
169
|
+
"text_length": 5420,
|
|
170
|
+
"preserve_layout": preserve_layout,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async def extract_tables(self, pdf_path: str, pages: str = "all",
|
|
174
|
+
output_format: str = "json") -> Dict[str, Any]:
|
|
175
|
+
return {
|
|
176
|
+
"pdf_path": pdf_path,
|
|
177
|
+
"tables_found": 3,
|
|
178
|
+
"output_format": output_format,
|
|
179
|
+
"total_rows": 250,
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async def extract_images(self, pdf_path: str, pages: str = "all",
|
|
183
|
+
image_format: str = "png") -> Dict[str, Any]:
|
|
184
|
+
return {
|
|
185
|
+
"pdf_path": pdf_path,
|
|
186
|
+
"images_extracted": 5,
|
|
187
|
+
"image_format": image_format,
|
|
188
|
+
"total_size_mb": 12.5,
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async def apply_ocr(self, pdf_path: str, pages: str = "all",
|
|
192
|
+
language: str = "en") -> Dict[str, Any]:
|
|
193
|
+
return {
|
|
194
|
+
"pdf_path": pdf_path,
|
|
195
|
+
"pages_ocr": 10,
|
|
196
|
+
"language": language,
|
|
197
|
+
"confidence": 0.92,
|
|
198
|
+
"text_length": 5500,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async def detect_document_structure(self, pdf_path: str) -> Dict[str, Any]:
|
|
202
|
+
return {
|
|
203
|
+
"pdf_path": pdf_path,
|
|
204
|
+
"structure": {
|
|
205
|
+
"headers": 5,
|
|
206
|
+
"sections": 12,
|
|
207
|
+
"lists": 3,
|
|
208
|
+
"tables": 2,
|
|
209
|
+
},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async def extract_metadata(self, pdf_path: str) -> Dict[str, Any]:
|
|
213
|
+
return {
|
|
214
|
+
"pdf_path": pdf_path,
|
|
215
|
+
"title": "Document Title",
|
|
216
|
+
"author": "Author Name",
|
|
217
|
+
"pages": 10,
|
|
218
|
+
"created": "2024-01-15T00:00:00Z",
|
|
219
|
+
"modified": "2024-07-31T00:00:00Z",
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async def detect_forms(self, pdf_path: str) -> Dict[str, Any]:
|
|
223
|
+
return {
|
|
224
|
+
"pdf_path": pdf_path,
|
|
225
|
+
"is_form": True,
|
|
226
|
+
"fields": 15,
|
|
227
|
+
"field_types": {"text": 10, "checkbox": 3, "dropdown": 2},
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async def chunk_document(self, pdf_path: str, chunk_strategy: str = "semantic",
|
|
231
|
+
chunk_size: int = 1000) -> Dict[str, Any]:
|
|
232
|
+
return {
|
|
233
|
+
"pdf_path": pdf_path,
|
|
234
|
+
"strategy": chunk_strategy,
|
|
235
|
+
"chunks": 12,
|
|
236
|
+
"avg_chunk_size": 1000,
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async def extract_citations(self, pdf_path: str) -> Dict[str, Any]:
|
|
240
|
+
return {
|
|
241
|
+
"pdf_path": pdf_path,
|
|
242
|
+
"citations": 25,
|
|
243
|
+
"references": [
|
|
244
|
+
{"text": "Smith et al. (2020)", "cited_by": 5}
|
|
245
|
+
],
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async def detect_language(self, pdf_path: str) -> Dict[str, Any]:
|
|
249
|
+
return {
|
|
250
|
+
"pdf_path": pdf_path,
|
|
251
|
+
"primary_language": "en",
|
|
252
|
+
"confidence": 0.98,
|
|
253
|
+
"other_languages": [],
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async def validate_pdf(self, pdf_path: str) -> Dict[str, Any]:
|
|
257
|
+
return {
|
|
258
|
+
"pdf_path": pdf_path,
|
|
259
|
+
"is_valid": True,
|
|
260
|
+
"errors": [],
|
|
261
|
+
"warnings": ["Some images may be low resolution"],
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async def export_processed_document(self, pdf_path: str,
|
|
265
|
+
output_format: str) -> Dict[str, Any]:
|
|
266
|
+
return {
|
|
267
|
+
"pdf_path": pdf_path,
|
|
268
|
+
"output_format": output_format,
|
|
269
|
+
"filename": f"output.{output_format.lower()}",
|
|
270
|
+
"size_mb": 2.5,
|
|
271
|
+
}
|