langgraph-api 0.18.0.dev7__py3-none-any.whl → 0.18.0.dev8__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.
langgraph_api/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.18.0.dev7"
1
+ __version__ = "0.18.0.dev8"
@@ -0,0 +1,52 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from typing import TYPE_CHECKING
6
+ from urllib.parse import urlsplit
7
+
8
+ from langgraph_api.config import ANY_ORIGIN
9
+
10
+ if TYPE_CHECKING:
11
+ from langgraph_api.config.schemas import CorsConfig
12
+
13
+ LOOPBACK_HOSTS = frozenset({"localhost", "127.0.0.1", "::1"})
14
+ WEB_SCHEMES = frozenset({"http", "https"})
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class OriginPolicy:
19
+ listed: frozenset[str]
20
+ pattern: re.Pattern[str] | None
21
+
22
+ @classmethod
23
+ def of(cls, settings: CorsConfig) -> OriginPolicy:
24
+ regex = settings.get("allow_origin_regex")
25
+ return cls(
26
+ frozenset(settings.get("allow_origins", ())) - {ANY_ORIGIN},
27
+ re.compile(regex) if regex is not None else None,
28
+ )
29
+
30
+ def allows(self, origin: str | None) -> bool:
31
+ return (
32
+ origin is None
33
+ or origin in self.listed
34
+ or (self.pattern is not None and self.pattern.fullmatch(origin) is not None)
35
+ or _bare_origin_host(origin) in LOOPBACK_HOSTS
36
+ )
37
+
38
+
39
+ def _bare_origin_host(origin: str) -> str | None:
40
+ try:
41
+ parsed = urlsplit(origin)
42
+ except ValueError:
43
+ return None
44
+ if (
45
+ parsed.scheme not in WEB_SCHEMES
46
+ or "@" in parsed.netloc
47
+ or parsed.path
48
+ or parsed.query
49
+ or parsed.fragment
50
+ ):
51
+ return None
52
+ return parsed.hostname or None
@@ -1,22 +1,27 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from http import HTTPStatus
4
+
3
5
  from starlette.responses import Response
4
6
 
5
7
  from langgraph_api.api.mcp._handlers import (
8
+ create_error_response,
6
9
  handle_delete_request,
7
10
  handle_get_request,
8
11
  handle_post_request,
9
12
  )
13
+ from langgraph_api.api.mcp._origins import OriginPolicy
14
+ from langgraph_api.config import CORS_SETTINGS
10
15
  from langgraph_api.route import ApiRequest, ApiRoute
11
16
 
12
17
  __all__ = ["mcp_routes"]
13
18
 
19
+ ORIGIN_POLICY = OriginPolicy.of(CORS_SETTINGS)
20
+
14
21
 
15
22
  async def handle_mcp_endpoint(request: ApiRequest) -> Response:
16
- # MCP endpoint handler that implements the Streamable HTTP protocol.
17
- # Supports POST (JSON-RPC request) and DELETE (terminate session).
18
- # GET (streaming session resumption) and text/event-stream not yet supported.
19
- # Route request based on HTTP method
23
+ if not ORIGIN_POLICY.allows(request.headers.get("origin")):
24
+ return create_error_response("Origin is not allowed", HTTPStatus.FORBIDDEN)
20
25
  if request.method == "DELETE":
21
26
  return handle_delete_request()
22
27
  elif request.method == "GET":
@@ -24,11 +29,9 @@ async def handle_mcp_endpoint(request: ApiRequest) -> Response:
24
29
  elif request.method == "POST":
25
30
  return await handle_post_request(request)
26
31
  else:
27
- # Method not allowed
28
32
  return Response(status_code=405)
29
33
 
30
34
 
31
- # Define routes for the MCP endpoint
32
35
  mcp_routes = [
33
36
  ApiRoute("/mcp", handle_mcp_endpoint, methods=["GET", "POST", "DELETE"]),
34
37
  ]
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from collections.abc import Sequence
2
3
  from enum import StrEnum
3
4
  from os import environ, getenv
4
5
  from typing import TYPE_CHECKING, Annotated, Literal, TypeVar, cast
@@ -363,7 +364,10 @@ MOUNT_PREFIX: str | None = _validate_mount_prefix(
363
364
  or (HTTP_CONFIG.get("mount_prefix") if HTTP_CONFIG else None)
364
365
  )
365
366
 
366
- CORS_ALLOW_ORIGINS = env("CORS_ALLOW_ORIGINS", cast=CommaSeparatedStrings, default="*")
367
+ ANY_ORIGIN = "*"
368
+ CORS_ALLOW_ORIGINS = env(
369
+ "CORS_ALLOW_ORIGINS", cast=CommaSeparatedStrings, default=ANY_ORIGIN
370
+ )
367
371
  CORS_CONFIG = env(
368
372
  "CORS_CONFIG", cast=_parse.parse_schema(CorsConfig), default=None
369
373
  ) or (HTTP_CONFIG.get("cors") if HTTP_CONFIG else None)
@@ -406,12 +410,35 @@ CORS_CONFIG = env(
406
410
  }
407
411
  }
408
412
  """
409
- if (
410
- CORS_CONFIG is not None
411
- and CORS_ALLOW_ORIGINS != "*"
412
- and CORS_CONFIG.get("allow_origins") is None
413
- ):
414
- CORS_CONFIG["allow_origins"] = CORS_ALLOW_ORIGINS
413
+
414
+
415
+ def _cors_settings(
416
+ cors_config: CorsConfig | None, allow_origins: Sequence[str]
417
+ ) -> CorsConfig:
418
+ origins = list(allow_origins)
419
+ if cors_config is None:
420
+ return {
421
+ "allow_origins": origins,
422
+ "allow_credentials": ANY_ORIGIN not in origins,
423
+ "allow_methods": ["*"],
424
+ "allow_headers": ["*"],
425
+ "expose_headers": [
426
+ "x-pagination-total",
427
+ "x-pagination-next",
428
+ "content-location",
429
+ "location",
430
+ ],
431
+ }
432
+ keeps_its_own_origins = cors_config.get("allow_origins") is not None
433
+ wildcard_would_widen_regex = cors_config.get(
434
+ "allow_origin_regex"
435
+ ) is not None and origins == [ANY_ORIGIN]
436
+ if keeps_its_own_origins or wildcard_would_widen_regex:
437
+ return cors_config
438
+ return {**cors_config, "allow_origins": origins}
439
+
440
+
441
+ CORS_SETTINGS = _cors_settings(CORS_CONFIG, CORS_ALLOW_ORIGINS)
415
442
 
416
443
  # queue
417
444
 
@@ -794,6 +821,7 @@ traceblock.patch_requests()
794
821
  __all__ = [
795
822
  "AES_JSON_DISALLOWED_KEYS",
796
823
  "ALLOW_PRIVATE_NETWORK",
824
+ "ANY_ORIGIN",
797
825
  "API_VARIANT",
798
826
  "BG_JOB_HEARTBEAT",
799
827
  "BG_JOB_ISOLATED_LOOPS",
@@ -810,6 +838,7 @@ __all__ = [
810
838
  "CHECKPOINT_MAX_BATCH_SIZE",
811
839
  "CORS_ALLOW_ORIGINS",
812
840
  "CORS_CONFIG",
841
+ "CORS_SETTINGS",
813
842
  "CRON_SCHEDULER_BATCH_SIZE",
814
843
  "CRON_SCHEDULER_SLEEP_TIME",
815
844
  "DATABASE_URI",
@@ -670,6 +670,7 @@ const StreamEventsPayload = z.object({
670
670
  // keeps the legacy ``on_chain_stream`` behavior used by the older
671
671
  // streaming endpoints.
672
672
  stream_protocol: z.enum(["v2", "v3"]).nullish(),
673
+ output_keys: z.union([z.string(), z.array(z.string())]).nullish(),
673
674
  });
674
675
 
675
676
  function reviveCommand(
@@ -798,6 +799,10 @@ async function* streamEventsRequest(
798
799
  ...config,
799
800
  signal,
800
801
  version: "v3",
802
+ outputKeys:
803
+ payload.output_keys === undefined
804
+ ? graph.outputChannels
805
+ : (payload.output_keys ?? undefined),
801
806
  interruptBefore,
802
807
  interruptAfter,
803
808
  ...(callbacks ? { callbacks } : {}),
langgraph_api/server.py CHANGED
@@ -66,25 +66,7 @@ if JS_PROXY_MIDDLEWARE_ENABLED:
66
66
 
67
67
  global_middleware.extend(
68
68
  [
69
- (
70
- Middleware(
71
- CORSMiddleware,
72
- allow_origins=config.CORS_ALLOW_ORIGINS,
73
- allow_credentials="*" not in config.CORS_ALLOW_ORIGINS,
74
- allow_methods=["*"],
75
- allow_headers=["*"],
76
- expose_headers=[
77
- "x-pagination-total",
78
- "x-pagination-next",
79
- "content-location",
80
- ],
81
- )
82
- if config.CORS_CONFIG is None
83
- else Middleware(
84
- CORSMiddleware,
85
- **config.CORS_CONFIG,
86
- )
87
- ),
69
+ Middleware(CORSMiddleware, **config.CORS_SETTINGS),
88
70
  Middleware(AccessLoggerMiddleware, logger=logger),
89
71
  Middleware(RequestIdMiddleware, mount_prefix=config.MOUNT_PREFIX),
90
72
  ]
@@ -226,6 +226,7 @@ async def astream_state_v2(
226
226
  # langgraph >=1.2.0a6. ``stream_mode`` was already popped by the
227
227
  # caller.
228
228
  kwargs.pop("subgraphs", None)
229
+ kwargs.setdefault("output_keys", graph.output_channels)
229
230
  # ``version="v3"`` and the awaitable return are alpha-only;
230
231
  # ``capabilities.py`` gates the v3 path at runtime so older
231
232
  # langgraph never reaches this call.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.18.0.dev7
3
+ Version: 0.18.0.dev8
4
4
  Author-email: Will Fu-Hinthorn <will@langchain.dev>, Josh Rogers <josh@langchain.dev>, Parker Rule <parker@langchain.dev>
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
@@ -1,4 +1,4 @@
1
- langgraph_api/__init__.py,sha256=f2wN1tmyQm9TIb2ahNCBhJpCy_sGqbls-x1eji-fGtM,28
1
+ langgraph_api/__init__.py,sha256=xDWSz-IdnwPg2_CLKonqvJoez_uokIczJ46zgJXmT9s,28
2
2
  langgraph_api/_factory_utils.py,sha256=_FqW0olMu2PGLsnRq0ylt3a4jDCe12YIFHExMXI6Nyo,5976
3
3
  langgraph_api/asgi_transport.py,sha256=XApY3lIWBZTMbbsl8dDJzl0cLGirmAGE0SifqZUnXvs,11896
4
4
  langgraph_api/asyncio.py,sha256=-p-Vx89TnErypdPcWb-2EdbsSvhdIdHfriCFG7UP7ls,12444
@@ -28,12 +28,12 @@ langgraph_api/schema.py,sha256=_Eh_P87jSvH-Ec23wIDTEageeEwXNK3Dck53ykvXpAU,11101
28
28
  langgraph_api/self_hosted_logs.py,sha256=CSWJH-Qrt99AFQw1-8u78wgKDkbyByQ-0tXfP2PDzGs,6096
29
29
  langgraph_api/self_hosted_metrics.py,sha256=pv2E70kWxShO90N5fsIPOg28HGiOD7_zlvVV9FmZ7eY,16806
30
30
  langgraph_api/serde.py,sha256=sMjrORY2WCNmZEXDaE3lydi1un686qR1E_Wxw4mFgjU,9144
31
- langgraph_api/server.py,sha256=ryMqYF12iur4cThgjQQHnqEHdTcN08Ui_MQ8PGY2i0M,10869
31
+ langgraph_api/server.py,sha256=_DG7dCL2wtlaXdppTce2e0k8PunCcSRj79ncQfqmK_Q,10305
32
32
  langgraph_api/sse.py,sha256=cChZ7raQUHp8p5BreE_5wMBR8lFO0n7746sV8_HQOrc,4822
33
33
  langgraph_api/state.py,sha256=GbehlusbwoOAOHp630mXxwLP7Kh-UxJA7xaZ6EgYaRE,6239
34
34
  langgraph_api/store.py,sha256=OXcdQSVCDKOzfS8S2aJHXbfuI5iYRoQBiW9WYKnCyx8,7122
35
35
  langgraph_api/stream.py,sha256=SMrditrbTJ7QtKW-ThpYAPiVsQD-ot_tQeGJIp9PKGI,36519
36
- langgraph_api/stream_v2.py,sha256=cmbE566boEgKkjcqqf8OvDqjWh8VzvCCsTTlvUF5Drg,10762
36
+ langgraph_api/stream_v2.py,sha256=AieeCAXQsthy9n6crCibMB7unbN1W_o1xkpJ2v11RSY,10822
37
37
  langgraph_api/traceblock.py,sha256=GhgAtLhAQ8Z8LHtRQq3par-rfPpRBQp2Q7WBACJq1NI,677
38
38
  langgraph_api/tracing_session.py,sha256=gmfv-BW1XRar6suVur1P4rTVKWkjElDEb2hhkBduWOs,728
39
39
  langgraph_api/validation.py,sha256=_fmqjbETfsrc4KKJLtfhVjELD-1WzEVrK6G1pfDrmtk,14930
@@ -65,9 +65,10 @@ langgraph_api/api/mcp/_calls.py,sha256=v1sjij5TjKHNHfZq5Rulah71MQhw6dJNs46eUb74E
65
65
  langgraph_api/api/mcp/_constants.py,sha256=O2-6zsmlzJ3y5ytEp_QiZ5JrmWoQXfIQLdUSTbLFH2Y,795
66
66
  langgraph_api/api/mcp/_handlers.py,sha256=fkVtUvs6Kc48lTgnsugMMQquMPMcnq0y6ilq1mW_uYs,11625
67
67
  langgraph_api/api/mcp/_models.py,sha256=uVzpGetNAHGFP3PaZdMYbuqO2x3-2del67CHwXPLaGU,618
68
+ langgraph_api/api/mcp/_origins.py,sha256=EmYgeG1mKC-mpK8W6mfnK7TIbn92JIsrTFswHE_81Hs,1429
68
69
  langgraph_api/api/mcp/_outcome.py,sha256=rRcvAbULo-w6c6OW60ZqK71Oqi2EpjdnI9ebe6267Ck,1089
69
70
  langgraph_api/api/mcp/_protocol.py,sha256=DdmOAjwj7t-3iP7S5hGm9Ib1WE7LBxJsNEbSDCAMv74,7495
70
- langgraph_api/api/mcp/_routes.py,sha256=y8gCPZAJc3YrjHA0a6PtqHKJ17HeN9oGKAYqC4ZBqaA,1057
71
+ langgraph_api/api/mcp/_routes.py,sha256=7-jOhV6XtPK0iWeV6R0_duRXlXSfvw_qd1YUV62mULY,1079
71
72
  langgraph_api/api/mcp/_sanitizers.py,sha256=CmfM3cHtzSbrnZeYf4GcTqVPgZV99Y0kZ7mAzDCART8,2403
72
73
  langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
73
74
  langgraph_api/auth/custom.py,sha256=g0YP1UQESV9c4zKfKDGTz1R7xHqew5AKUL43YlNKJsU,31085
@@ -78,7 +79,7 @@ langgraph_api/auth/studio_user.py,sha256=Lx3eMsRA8gxZ3--ApL09hDW84A6qIncaZoBZjSg
78
79
  langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
80
  langgraph_api/auth/langsmith/backend.py,sha256=gaSuZC8JO32XHe8IdqttNpBGO6PCVrtXaq1Negc7vX0,3857
80
81
  langgraph_api/auth/langsmith/client.py,sha256=79kwCVeHU64nsHsxWipfZhf44lM6vfs2nlfTxlJF6LU,4142
81
- langgraph_api/config/__init__.py,sha256=NEpY6lhuZs26DHS_lrRhV5CF1dlrXOs0RgFrEBsmWvE,32465
82
+ langgraph_api/config/__init__.py,sha256=5VGNpSl0FjWLJaO8a-WxDgu0IIneOypuBj_Q1YVktYA,33351
82
83
  langgraph_api/config/_parse.py,sha256=RpfZgFAPJlRMv13AzGr3kAYbIrqHcgjzO8IgeboTw4A,3922
83
84
  langgraph_api/config/schemas.py,sha256=RaV-huiOALaJDsGi-wVp7Ghz8aE7UDXgL9YB2TYvQKA,21439
84
85
  langgraph_api/encryption/__init__.py,sha256=gaCZ00CocSbqSqrDn6XJHaSp2CZCnC8qnrD9G4fbzyI,363
@@ -118,7 +119,7 @@ langgraph_api/js/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
118
119
  langgraph_api/js/base.py,sha256=CJihwc51MwOVkis80f8zudRa1fQz_5jrom4rY8trww8,1133
119
120
  langgraph_api/js/build.mts,sha256=--Kfm4NKDhMljmv8fVvRR0VeUsBKiWch6qXpBoKmrvU,3445
120
121
  langgraph_api/js/client.http.mts,sha256=ARlTJURLJamgvyaPlBCYEAHFnXQnPpzzUXWKwXiebnw,6011
121
- langgraph_api/js/client.mts,sha256=UwUK7MVT8gE-kG2seq--rhKALFifcNqY6cIYv0lXmxI,40098
122
+ langgraph_api/js/client.mts,sha256=vEXetaza6XmCH2lFyXueb-iYbrHhWrETf8UpZp5vdwM,40316
122
123
  langgraph_api/js/errors.py,sha256=Cm1TKWlUCwZReDC5AQ6SgNIVGD27Qov2xcgHyf8-GXo,361
123
124
  langgraph_api/js/global.d.ts,sha256=j4GhgtQSZ5_cHzjSPcHgMJ8tfBThxrH-pUOrrJGteOU,196
124
125
  langgraph_api/js/package.json,sha256=3Li36Qe_oCR85ppPDj0i86ZfhBxDWms35Izy8F73XdY,1362
@@ -259,8 +260,8 @@ langgraph_grpc_common/proto/store_pb2.py,sha256=VWJ7wtzL9pVmZZKbxMzOzCoiFn58bB2g
259
260
  langgraph_grpc_common/proto/store_pb2.pyi,sha256=atXejijuYEb9tE0stGQdZcfA9ucMFj-ytTMwvkxwviM,29322
260
261
  langgraph_grpc_common/proto/store_pb2_grpc.py,sha256=Kyt8eV2NXkjh3be_BYytGdD19kNf74961cruonMvCrw,3300
261
262
  langgraph_grpc_common/proto/store_pb2_grpc.pyi,sha256=oS1h2cDoq2OjyM8xazcmc8LwRDC5oCAD0zto2QUmPQw,2027
262
- langgraph_api-0.18.0.dev7.dist-info/METADATA,sha256=vVfew1JjgVNz9qz7DovPryT59-5NK321Oo_FNaMImwA,5879
263
- langgraph_api-0.18.0.dev7.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
264
- langgraph_api-0.18.0.dev7.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
265
- langgraph_api-0.18.0.dev7.dist-info/licenses/LICENSE,sha256=SCVQGLQfwOllsRFa9-Z3m8IYu4pnR9Vh2oANUCJiKqI,3860
266
- langgraph_api-0.18.0.dev7.dist-info/RECORD,,
263
+ langgraph_api-0.18.0.dev8.dist-info/METADATA,sha256=FSvjKL-CVqhapSc-r3R-uBZXt3zJgOTQLCjg_gyNIII,5879
264
+ langgraph_api-0.18.0.dev8.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
265
+ langgraph_api-0.18.0.dev8.dist-info/entry_points.txt,sha256=hGedv8n7cgi41PypMfinwS_HfCwA7xJIfS0jAp8htV8,78
266
+ langgraph_api-0.18.0.dev8.dist-info/licenses/LICENSE,sha256=SCVQGLQfwOllsRFa9-Z3m8IYu4pnR9Vh2oANUCJiKqI,3860
267
+ langgraph_api-0.18.0.dev8.dist-info/RECORD,,