deepresearch-flow 0.7.2__py3-none-any.whl → 0.7.3__py3-none-any.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.
- deepresearch_flow/paper/snapshot/api.py +4 -3
- deepresearch_flow/paper/snapshot/mcp_server.py +38 -13
- deepresearch_flow/paper/snapshot/tests/test_mcp_transport.py +52 -0
- {deepresearch_flow-0.7.2.dist-info → deepresearch_flow-0.7.3.dist-info}/METADATA +25 -6
- {deepresearch_flow-0.7.2.dist-info → deepresearch_flow-0.7.3.dist-info}/RECORD +9 -8
- {deepresearch_flow-0.7.2.dist-info → deepresearch_flow-0.7.3.dist-info}/WHEEL +0 -0
- {deepresearch_flow-0.7.2.dist-info → deepresearch_flow-0.7.3.dist-info}/entry_points.txt +0 -0
- {deepresearch_flow-0.7.2.dist-info → deepresearch_flow-0.7.3.dist-info}/licenses/LICENSE +0 -0
- {deepresearch_flow-0.7.2.dist-info → deepresearch_flow-0.7.3.dist-info}/top_level.txt +0 -0
|
@@ -908,7 +908,7 @@ def create_app(
|
|
|
908
908
|
# Lazy import to avoid circular dependency
|
|
909
909
|
from deepresearch_flow.paper.snapshot.mcp_server import (
|
|
910
910
|
McpSnapshotConfig,
|
|
911
|
-
|
|
911
|
+
create_mcp_apps,
|
|
912
912
|
resolve_static_export_dir,
|
|
913
913
|
)
|
|
914
914
|
|
|
@@ -919,7 +919,7 @@ def create_app(
|
|
|
919
919
|
limits=limits or ApiLimits(),
|
|
920
920
|
origin_allowlist=cors_allowed_origins or ["*"],
|
|
921
921
|
)
|
|
922
|
-
|
|
922
|
+
mcp_apps, mcp_lifespan = create_mcp_apps(mcp_config)
|
|
923
923
|
|
|
924
924
|
routes = [
|
|
925
925
|
Route("/api/v1/config", _api_config, methods=["GET"]),
|
|
@@ -931,7 +931,8 @@ def create_app(
|
|
|
931
931
|
Route("/api/v1/facets/{facet:str}/{facet_id:str}/stats", _api_facet_stats, methods=["GET"]),
|
|
932
932
|
Route("/api/v1/facets/{facet:str}/by-value/{value:str}/papers", _api_facet_by_value_papers, methods=["GET"]),
|
|
933
933
|
Route("/api/v1/facets/{facet:str}/by-value/{value:str}/stats", _api_facet_by_value_stats, methods=["GET"]),
|
|
934
|
-
Mount("/mcp", app=
|
|
934
|
+
Mount("/mcp", app=mcp_apps["streamable-http"]),
|
|
935
|
+
Mount("/mcp-sse", app=mcp_apps["sse"]),
|
|
935
936
|
]
|
|
936
937
|
|
|
937
938
|
# Pass MCP lifespan to ensure session manager initializes properly
|
|
@@ -5,7 +5,7 @@ import json
|
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
import re
|
|
8
|
-
from typing import Any
|
|
8
|
+
from typing import Any, Literal
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
from starlette.applications import Starlette
|
|
@@ -76,14 +76,13 @@ class McpSnapshotConfig:
|
|
|
76
76
|
|
|
77
77
|
|
|
78
78
|
class McpRequestGuardMiddleware(BaseHTTPMiddleware):
|
|
79
|
-
def __init__(self, app, *, origin_allowlist: list[str]) -> None:
|
|
79
|
+
def __init__(self, app, *, origin_allowlist: list[str], allowed_methods: set[str] | None = None) -> None:
|
|
80
80
|
super().__init__(app)
|
|
81
81
|
self._allowlist = [origin.lower() for origin in origin_allowlist]
|
|
82
|
+
self._allowed_methods = {method.upper() for method in (allowed_methods or {"POST", "OPTIONS"})}
|
|
82
83
|
|
|
83
84
|
async def dispatch(self, request: Request, call_next): # type: ignore[override]
|
|
84
|
-
if request.method
|
|
85
|
-
return Response("Method Not Allowed", status_code=405)
|
|
86
|
-
if request.method not in {"POST", "OPTIONS"}:
|
|
85
|
+
if request.method.upper() not in self._allowed_methods:
|
|
87
86
|
return Response("Method Not Allowed", status_code=405)
|
|
88
87
|
origin = request.headers.get("origin")
|
|
89
88
|
if origin and not self._is_allowed_origin(origin):
|
|
@@ -108,23 +107,49 @@ def configure(config: McpSnapshotConfig) -> None:
|
|
|
108
107
|
_CONFIG = config
|
|
109
108
|
|
|
110
109
|
|
|
111
|
-
def
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
def _allowed_methods_for_transport(transport: Literal["streamable-http", "sse"]) -> set[str]:
|
|
111
|
+
if transport == "sse":
|
|
112
|
+
return {"GET", "POST", "OPTIONS"}
|
|
113
|
+
return {"POST", "OPTIONS"}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def create_mcp_transport_app(
|
|
117
|
+
config: McpSnapshotConfig,
|
|
118
|
+
*,
|
|
119
|
+
transport: Literal["streamable-http", "sse"] = "streamable-http",
|
|
120
|
+
) -> tuple[Starlette, Any]:
|
|
121
|
+
"""Create MCP app for a specific transport with transport-aware method guard."""
|
|
117
122
|
configure(config)
|
|
118
|
-
mcp_app = mcp.http_app(path="/", stateless_http=
|
|
123
|
+
mcp_app = mcp.http_app(path="/", transport=transport, stateless_http=(transport == "streamable-http"))
|
|
119
124
|
wrapped = Starlette(
|
|
120
125
|
routes=[Mount("/", app=mcp_app)],
|
|
121
126
|
middleware=[
|
|
122
|
-
Middleware(
|
|
127
|
+
Middleware(
|
|
128
|
+
McpRequestGuardMiddleware,
|
|
129
|
+
origin_allowlist=config.origin_allowlist,
|
|
130
|
+
allowed_methods=_allowed_methods_for_transport(transport),
|
|
131
|
+
),
|
|
123
132
|
],
|
|
124
133
|
)
|
|
125
134
|
return wrapped, mcp_app.lifespan
|
|
126
135
|
|
|
127
136
|
|
|
137
|
+
def create_mcp_apps(config: McpSnapshotConfig) -> tuple[dict[str, Starlette], Any]:
|
|
138
|
+
"""Create streamable-http and sse MCP apps.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
A tuple of (apps_by_transport, lifespan_context).
|
|
142
|
+
"""
|
|
143
|
+
streamable_app, lifespan = create_mcp_transport_app(config, transport="streamable-http")
|
|
144
|
+
sse_app, _ = create_mcp_transport_app(config, transport="sse")
|
|
145
|
+
return {"streamable-http": streamable_app, "sse": sse_app}, lifespan
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def create_mcp_app(config: McpSnapshotConfig) -> tuple[Starlette, Any]:
|
|
149
|
+
"""Backward-compatible helper returning streamable-http MCP app."""
|
|
150
|
+
return create_mcp_transport_app(config, transport="streamable-http")
|
|
151
|
+
|
|
152
|
+
|
|
128
153
|
def _get_config() -> McpSnapshotConfig:
|
|
129
154
|
if _CONFIG is None:
|
|
130
155
|
raise RuntimeError("MCP server not configured")
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tempfile
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from deepresearch_flow.paper.snapshot.api import create_app
|
|
8
|
+
from deepresearch_flow.paper.snapshot.common import ApiLimits
|
|
9
|
+
from deepresearch_flow.paper.snapshot.mcp_server import (
|
|
10
|
+
McpSnapshotConfig,
|
|
11
|
+
_allowed_methods_for_transport,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestMcpTransport(unittest.TestCase):
|
|
16
|
+
@classmethod
|
|
17
|
+
def setUpClass(cls) -> None:
|
|
18
|
+
cls.tmpdir = tempfile.TemporaryDirectory()
|
|
19
|
+
cls.snapshot_db = Path(cls.tmpdir.name) / "snapshot.db"
|
|
20
|
+
cls.snapshot_db.touch()
|
|
21
|
+
cls.cfg = McpSnapshotConfig(
|
|
22
|
+
snapshot_db=cls.snapshot_db,
|
|
23
|
+
static_base_url="",
|
|
24
|
+
static_export_dir=None,
|
|
25
|
+
limits=ApiLimits(),
|
|
26
|
+
origin_allowlist=["*"],
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def tearDownClass(cls) -> None:
|
|
31
|
+
cls.tmpdir.cleanup()
|
|
32
|
+
|
|
33
|
+
def test_streamable_transport_rejects_get(self) -> None:
|
|
34
|
+
self.assertNotIn("GET", _allowed_methods_for_transport("streamable-http"))
|
|
35
|
+
|
|
36
|
+
def test_sse_transport_allows_get(self) -> None:
|
|
37
|
+
self.assertIn("GET", _allowed_methods_for_transport("sse"))
|
|
38
|
+
|
|
39
|
+
def test_api_mounts_streamable_and_sse_endpoints(self) -> None:
|
|
40
|
+
app = create_app(
|
|
41
|
+
snapshot_db=self.snapshot_db,
|
|
42
|
+
static_base_url="",
|
|
43
|
+
cors_allowed_origins=["*"],
|
|
44
|
+
limits=ApiLimits(),
|
|
45
|
+
)
|
|
46
|
+
mount_paths = sorted(getattr(route, "path", "") for route in app.routes)
|
|
47
|
+
self.assertIn("/mcp", mount_paths)
|
|
48
|
+
self.assertIn("/mcp-sse", mount_paths)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
if __name__ == "__main__":
|
|
52
|
+
unittest.main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepresearch-flow
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
4
4
|
Summary: Workflow tools for paper extraction, review, and research automation.
|
|
5
5
|
Author-email: DengQi <dengqi935@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -532,6 +532,22 @@ server {
|
|
|
532
532
|
proxy_set_header X-Real-IP $remote_addr;
|
|
533
533
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
534
534
|
}
|
|
535
|
+
|
|
536
|
+
# SSE transport for MCP clients that require Server-Sent Events
|
|
537
|
+
location ^~ /mcp-sse {
|
|
538
|
+
proxy_pass http://127.0.0.1:8001;
|
|
539
|
+
proxy_http_version 1.1;
|
|
540
|
+
proxy_set_header Connection "";
|
|
541
|
+
proxy_set_header Host $host;
|
|
542
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
543
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
544
|
+
proxy_buffering off;
|
|
545
|
+
proxy_cache off;
|
|
546
|
+
proxy_read_timeout 3600s;
|
|
547
|
+
proxy_send_timeout 3600s;
|
|
548
|
+
chunked_transfer_encoding off;
|
|
549
|
+
add_header X-Accel-Buffering no;
|
|
550
|
+
}
|
|
535
551
|
}
|
|
536
552
|
|
|
537
553
|
# Static assets (separate domain)
|
|
@@ -562,12 +578,15 @@ uv run deepresearch-flow paper db api serve \
|
|
|
562
578
|
--host 0.0.0.0 --port 8001
|
|
563
579
|
```
|
|
564
580
|
|
|
565
|
-
### 3.1) MCP (FastMCP Streamable HTTP)
|
|
581
|
+
### 3.1) MCP (FastMCP Streamable HTTP + SSE)
|
|
566
582
|
|
|
567
|
-
This project exposes
|
|
583
|
+
This project exposes MCP servers mounted on the snapshot API:
|
|
568
584
|
|
|
569
|
-
-
|
|
570
|
-
-
|
|
585
|
+
- Streamable HTTP endpoint: `http://<host>:8001/mcp`
|
|
586
|
+
- SSE endpoint: `http://<host>:8001/mcp-sse`
|
|
587
|
+
- Transport behavior:
|
|
588
|
+
- `/mcp`: Streamable HTTP via `POST` only (`GET` returns 405)
|
|
589
|
+
- `/mcp-sse`: SSE-enabled transport (supports `GET` handshake)
|
|
571
590
|
- Protocol header: optional `mcp-protocol-version` (`2025-03-26` or `2025-06-18`)
|
|
572
591
|
- Static reads: summary/source/translation are served as **text content** by reading snapshot static assets (local-first via `PAPER_DB_STATIC_EXPORT_DIR`, HTTP fallback via `PAPER_DB_STATIC_BASE` / `PAPER_DB_STATIC_BASE_URL`)
|
|
573
592
|
|
|
@@ -959,7 +978,7 @@ docker run --rm -p 8899:8899 \
|
|
|
959
978
|
```
|
|
960
979
|
|
|
961
980
|
Notes:
|
|
962
|
-
- nginx listens on 8899 and proxies `/api
|
|
981
|
+
- nginx listens on 8899 and proxies `/api`, `/mcp`, and `/mcp-sse` to the internal API at `127.0.0.1:8000`.
|
|
963
982
|
- Mount your snapshot DB to `/db/papers.db` inside the container.
|
|
964
983
|
- Mount snapshot static assets to `/static` when serving assets from this container (default `PAPER_DB_STATIC_BASE` is `/static`).
|
|
965
984
|
- If `PAPER_DB_STATIC_BASE` is a full URL (e.g. `https://static.example.com`), nginx still serves the frontend locally, while API responses use that external static base for asset links.
|
|
@@ -43,17 +43,18 @@ deepresearch_flow/paper/schemas/default_paper_schema.json,sha256=6h_2ayHolJj8JMn
|
|
|
43
43
|
deepresearch_flow/paper/schemas/eight_questions_schema.json,sha256=VFKKpdZkgPdQkYIW5jyrZQ7c2TlQZwB4svVWfoiwxdg,1005
|
|
44
44
|
deepresearch_flow/paper/schemas/three_pass_schema.json,sha256=8aNr4EdRiilxszIRBCC4hRNXrfIOcdnVW4Qhe6Fnh0o,689
|
|
45
45
|
deepresearch_flow/paper/snapshot/__init__.py,sha256=1VLO36xxDB3J5Yoo-HH9vyI-4ev2HcivXN0sNLg8O5k,102
|
|
46
|
-
deepresearch_flow/paper/snapshot/api.py,sha256=
|
|
46
|
+
deepresearch_flow/paper/snapshot/api.py,sha256=z1TJmFeMKr5ZiNbZT1xTueVqUqQJD0WhBplDHKlFXRo,37476
|
|
47
47
|
deepresearch_flow/paper/snapshot/builder.py,sha256=HbRcfNteMoP4RnQ4y2onZCm9XfnIvzXLn_EwsLZsDzY,38692
|
|
48
48
|
deepresearch_flow/paper/snapshot/common.py,sha256=KAhlGlPgabOCe9Faps8BoDqin71qpkCfaL_ADCr_9vg,917
|
|
49
49
|
deepresearch_flow/paper/snapshot/identity.py,sha256=k9x1EZPFBU1qgxzkTGvwVtDjLgcosmM_udPuvRLl0uI,7748
|
|
50
|
-
deepresearch_flow/paper/snapshot/mcp_server.py,sha256=
|
|
50
|
+
deepresearch_flow/paper/snapshot/mcp_server.py,sha256=c_WrM7PIMGRmvLg_3759NXc8wH5iwLCa6REBAnngwRg,24491
|
|
51
51
|
deepresearch_flow/paper/snapshot/schema.py,sha256=DcVmAklLYyEeDoVV9jYw7hoMHnHd9Eziivl-LP2busY,8991
|
|
52
52
|
deepresearch_flow/paper/snapshot/text.py,sha256=0RnxLowa6AdirdLsUYym6BhWbjwiP2Qj2oZeA-pjmdE,4368
|
|
53
53
|
deepresearch_flow/paper/snapshot/unpacker.py,sha256=ScKSFdrQLJHrITHe9KAxgAEH-vAAnXLolvW9zeJ3wsc,8575
|
|
54
54
|
deepresearch_flow/paper/snapshot/tests/__init__.py,sha256=G0IowrxHjGUIaqxcw6SvlcLFAtE5ZsleG6ECgd-sIdk,52
|
|
55
55
|
deepresearch_flow/paper/snapshot/tests/test_identity.py,sha256=KDFixAUU9l68KOum7gf1IrD0Oy18dBCSXG7RbJTqflA,4520
|
|
56
56
|
deepresearch_flow/paper/snapshot/tests/test_mcp_server_schema_compat.py,sha256=T7FtkKkGpZx5M7Z278F4iaQFfwS0_XXce_tRdTArt5k,7076
|
|
57
|
+
deepresearch_flow/paper/snapshot/tests/test_mcp_transport.py,sha256=Qh91te1XgzssTUfgCJUpq6Xjnw4tzhhr78bcI3Z4DpA,1622
|
|
57
58
|
deepresearch_flow/paper/templates/__init__.py,sha256=p8W6kINvrf-T2X6Ow4GMr28syVOorFuMn0pbmieVzAw,35
|
|
58
59
|
deepresearch_flow/paper/templates/deep_read.md.j2,sha256=vwVSPOzMBFIS72ez5XFBaKrDZGz0z32L3VGP6mNk434,4780
|
|
59
60
|
deepresearch_flow/paper/templates/deep_read_phi.md.j2,sha256=6Yz2Kxk0czGDPkZiWX3b87glLYHwDU1afr6CkjS-dh8,1666
|
|
@@ -467,9 +468,9 @@ deepresearch_flow/translator/placeholder.py,sha256=mEgqA-dPdOsIhno0h_hzfpXpY2asb
|
|
|
467
468
|
deepresearch_flow/translator/prompts.py,sha256=EvfBvBIpQXARDj4m87GAyFXJGL8EJeahj_rOmp9mv68,5556
|
|
468
469
|
deepresearch_flow/translator/protector.py,sha256=yUMuS2FgVofK_MRXrcauLRiwNvdCCjNAnh6CcNd686o,11777
|
|
469
470
|
deepresearch_flow/translator/segment.py,sha256=rBFMCLTrvm2GrPc_hNFymi-8Ih2DAtUQlZHCRE9nLaM,5146
|
|
470
|
-
deepresearch_flow-0.7.
|
|
471
|
-
deepresearch_flow-0.7.
|
|
472
|
-
deepresearch_flow-0.7.
|
|
473
|
-
deepresearch_flow-0.7.
|
|
474
|
-
deepresearch_flow-0.7.
|
|
475
|
-
deepresearch_flow-0.7.
|
|
471
|
+
deepresearch_flow-0.7.3.dist-info/licenses/LICENSE,sha256=hT8F2Py1pe6flxq3Ufdm2UKFk0B8CBm0aAQfsLXfvjw,1063
|
|
472
|
+
deepresearch_flow-0.7.3.dist-info/METADATA,sha256=I8ERjmgnZ-IZ9WQvm6sPT_yKgozcL5g11sWgqh36Ti8,31955
|
|
473
|
+
deepresearch_flow-0.7.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
474
|
+
deepresearch_flow-0.7.3.dist-info/entry_points.txt,sha256=1uIKscs0YRMg_mFsg9NjsaTt4CvQqQ_-zGERUKhhL_Y,65
|
|
475
|
+
deepresearch_flow-0.7.3.dist-info/top_level.txt,sha256=qBl4RvPJNJUbL8CFfMNWxY0HpQLx5RlF_ko-z_aKpm0,18
|
|
476
|
+
deepresearch_flow-0.7.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|