pyfect-dev 0.1.0__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.
- pyfect/__init__.py +729 -0
- pyfect/_all.py +242 -0
- pyfect/_background.py +28 -0
- pyfect/_binary_codecs.py +124 -0
- pyfect/_cached.py +165 -0
- pyfect/_catch.py +128 -0
- pyfect/_catch_reason.py +574 -0
- pyfect/_concurrency.py +291 -0
- pyfect/_context_effects.py +163 -0
- pyfect/_context_managers.py +76 -0
- pyfect/_core.py +368 -0
- pyfect/_digits.py +71 -0
- pyfect/_effect_args.py +46 -0
- pyfect/_effect_base.py +71 -0
- pyfect/_effect_concurrency.py +133 -0
- pyfect/_effect_eager.py +103 -0
- pyfect/_effect_errors.py +344 -0
- pyfect/_effect_folding.py +110 -0
- pyfect/_effect_hash.py +140 -0
- pyfect/_effect_logging.py +57 -0
- pyfect/_effect_metrics.py +102 -0
- pyfect/_effect_resources.py +179 -0
- pyfect/_effect_services.py +25 -0
- pyfect/_effect_timing.py +199 -0
- pyfect/_effect_tracing.py +99 -0
- pyfect/_equal.py +195 -0
- pyfect/_extras.py +43 -0
- pyfect/_fiber.py +969 -0
- pyfect/_fiber_all.py +134 -0
- pyfect/_fiber_collections.py +106 -0
- pyfect/_fn.py +706 -0
- pyfect/_hamt.py +174 -0
- pyfect/_interrupt_reason.py +33 -0
- pyfect/_isaac.py +149 -0
- pyfect/_iterables.py +231 -0
- pyfect/_js.py +263 -0
- pyfect/_js_date.py +87 -0
- pyfect/_json_text.py +286 -0
- pyfect/_json_value.py +9 -0
- pyfect/_layer_core.py +403 -0
- pyfect/_layer_for_life.py +33 -0
- pyfect/_layer_tracing.py +70 -0
- pyfect/_locked_ref.py +147 -0
- pyfect/_loop_thread.py +152 -0
- pyfect/_metric_ref.py +29 -0
- pyfect/_one_or_many.py +14 -0
- pyfect/_os_wait.py +118 -0
- pyfect/_path_posix.py +129 -0
- pyfect/_primitives.py +434 -0
- pyfect/_public.py +36 -0
- pyfect/_reasons.py +64 -0
- pyfect/_released.py +67 -0
- pyfect/_root.py +87 -0
- pyfect/_round_robin.py +73 -0
- pyfect/_some.py +17 -0
- pyfect/_stack.py +52 -0
- pyfect/_stack_frame.py +114 -0
- pyfect/_string_hash.py +35 -0
- pyfect/_subscriber_queue.py +47 -0
- pyfect/_tags.py +14 -0
- pyfect/_tracer_ref.py +21 -0
- pyfect/_tx_ops.py +77 -0
- pyfect/_unsubscripted.py +23 -0
- pyfect/_url.py +54 -0
- pyfect/_waiters.py +127 -0
- pyfect/_wake_tracking.py +98 -0
- pyfect/_wire_codec.py +123 -0
- pyfect/ai/__init__.py +73 -0
- pyfect/ai/_approvals.py +249 -0
- pyfect/ai/_decision_answers.py +185 -0
- pyfect/ai/_generated_client.py +298 -0
- pyfect/ai/_generated_support.py +84 -0
- pyfect/ai/_mcp/__init__.py +1 -0
- pyfect/ai/_mcp/batch.py +68 -0
- pyfect/ai/_mcp/client_transport.py +412 -0
- pyfect/ai/_mcp/client_wire.py +284 -0
- pyfect/ai/_mcp/core.py +490 -0
- pyfect/ai/_mcp/dispatch.py +1048 -0
- pyfect/ai/_mcp/http.py +462 -0
- pyfect/ai/_mcp/http_admission.py +269 -0
- pyfect/ai/_mcp/http_status.py +27 -0
- pyfect/ai/_mcp/input_wire.py +226 -0
- pyfect/ai/_mcp/log_levels.py +55 -0
- pyfect/ai/_mcp/param_headers.py +63 -0
- pyfect/ai/_mcp/projection.py +248 -0
- pyfect/ai/_mcp/register.py +683 -0
- pyfect/ai/_mcp/reverse_policy.py +155 -0
- pyfect/ai/_mcp/routing_header.py +24 -0
- pyfect/ai/_mcp/sessions.py +114 -0
- pyfect/ai/_mcp/stdio.py +110 -0
- pyfect/ai/_mcp/subscriptions.py +134 -0
- pyfect/ai/_mcp/tool_schema.py +35 -0
- pyfect/ai/_noul_answers.py +64 -0
- pyfect/ai/_openai_all_of.py +137 -0
- pyfect/ai/_openai_errors.py +233 -0
- pyfect/ai/_provider.py +439 -0
- pyfect/ai/_provider_tools.py +129 -0
- pyfect/ai/_stream_resolution.py +231 -0
- pyfect/ai/_structured_output_checks.py +138 -0
- pyfect/ai/_structured_output_codec.py +229 -0
- pyfect/ai/_structured_output_json.py +109 -0
- pyfect/ai/_tool_call_ids.py +80 -0
- pyfect/ai/ai_error.py +785 -0
- pyfect/ai/anthropic/__init__.py +28 -0
- pyfect/ai/anthropic/_errors.py +164 -0
- pyfect/ai/anthropic/_prompt.py +375 -0
- pyfect/ai/anthropic/_response.py +443 -0
- pyfect/ai/anthropic/_schema.py +206 -0
- pyfect/ai/anthropic/_stream.py +377 -0
- pyfect/ai/anthropic/_tools.py +248 -0
- pyfect/ai/anthropic/client.py +289 -0
- pyfect/ai/anthropic/config.py +32 -0
- pyfect/ai/anthropic/error.py +27 -0
- pyfect/ai/anthropic/language_model.py +366 -0
- pyfect/ai/anthropic/telemetry.py +51 -0
- pyfect/ai/anthropic/tool.py +570 -0
- pyfect/ai/anthropic_structured_output.py +138 -0
- pyfect/ai/chat.py +548 -0
- pyfect/ai/cloudflare/__init__.py +15 -0
- pyfect/ai/cloudflare/_errors.py +108 -0
- pyfect/ai/cloudflare/client.py +174 -0
- pyfect/ai/cloudflare/decision_model.py +117 -0
- pyfect/ai/cloudflare/schema.py +144 -0
- pyfect/ai/decision.py +290 -0
- pyfect/ai/decision_model.py +283 -0
- pyfect/ai/embedding_model.py +177 -0
- pyfect/ai/id_generator.py +79 -0
- pyfect/ai/language_model.py +1077 -0
- pyfect/ai/mcp_client.py +682 -0
- pyfect/ai/mcp_client_error.py +223 -0
- pyfect/ai/mcp_protocol.py +205 -0
- pyfect/ai/mcp_schema.py +1300 -0
- pyfect/ai/mcp_server.py +404 -0
- pyfect/ai/model.py +56 -0
- pyfect/ai/openai/__init__.py +49 -0
- pyfect/ai/openai/_errors.py +89 -0
- pyfect/ai/openai/_prompt.py +527 -0
- pyfect/ai/openai/_requests.py +34 -0
- pyfect/ai/openai/_response.py +569 -0
- pyfect/ai/openai/_schema.py +769 -0
- pyfect/ai/openai/_stream.py +771 -0
- pyfect/ai/openai/_tools.py +128 -0
- pyfect/ai/openai/_web_socket.py +295 -0
- pyfect/ai/openai/client.py +296 -0
- pyfect/ai/openai/client_generated.py +123 -0
- pyfect/ai/openai/config.py +34 -0
- pyfect/ai/openai/decision_model.py +177 -0
- pyfect/ai/openai/embedding_model.py +116 -0
- pyfect/ai/openai/error.py +8 -0
- pyfect/ai/openai/generated/__init__.py +7213 -0
- pyfect/ai/openai/generated/_recursive.py +18 -0
- pyfect/ai/openai/generated/_schemas_0.py +1015 -0
- pyfect/ai/openai/generated/_schemas_1.py +1016 -0
- pyfect/ai/openai/generated/_schemas_2.py +1017 -0
- pyfect/ai/openai/generated/_schemas_3.py +615 -0
- pyfect/ai/openai/generated/client.py +5298 -0
- pyfect/ai/openai/generated/types.py +8123 -0
- pyfect/ai/openai/language_model.py +304 -0
- pyfect/ai/openai/telemetry.py +19 -0
- pyfect/ai/openai/tool.py +391 -0
- pyfect/ai/openai_compat/__init__.py +25 -0
- pyfect/ai/openai_compat/_prompt.py +191 -0
- pyfect/ai/openai_compat/_request.py +153 -0
- pyfect/ai/openai_compat/_response.py +407 -0
- pyfect/ai/openai_compat/_schema.py +97 -0
- pyfect/ai/openai_compat/client.py +234 -0
- pyfect/ai/openai_compat/config.py +32 -0
- pyfect/ai/openai_compat/embedding_model.py +139 -0
- pyfect/ai/openai_compat/error.py +7 -0
- pyfect/ai/openai_compat/language_model.py +245 -0
- pyfect/ai/openai_compat/telemetry.py +51 -0
- pyfect/ai/openai_structured_output.py +118 -0
- pyfect/ai/openrouter/__init__.py +36 -0
- pyfect/ai/openrouter/_errors.py +186 -0
- pyfect/ai/openrouter/_prompt.py +272 -0
- pyfect/ai/openrouter/_response.py +226 -0
- pyfect/ai/openrouter/_stream.py +320 -0
- pyfect/ai/openrouter/client.py +283 -0
- pyfect/ai/openrouter/config.py +36 -0
- pyfect/ai/openrouter/decision_model.py +161 -0
- pyfect/ai/openrouter/error.py +23 -0
- pyfect/ai/openrouter/generated/__init__.py +3029 -0
- pyfect/ai/openrouter/generated/_recursive.py +15 -0
- pyfect/ai/openrouter/generated/_schemas_0.py +1015 -0
- pyfect/ai/openrouter/generated/_schemas_1.py +522 -0
- pyfect/ai/openrouter/generated/client.py +1445 -0
- pyfect/ai/openrouter/generated/types.py +4769 -0
- pyfect/ai/openrouter/language_model.py +345 -0
- pyfect/ai/openrouter/schema.py +165 -0
- pyfect/ai/prompt.py +664 -0
- pyfect/ai/response.py +768 -0
- pyfect/ai/response_id_tracker.py +82 -0
- pyfect/ai/telemetry.py +126 -0
- pyfect/ai/tokenizer.py +61 -0
- pyfect/ai/tool.py +536 -0
- pyfect/ai/toolkit.py +490 -0
- pyfect/ai/typesafe/__init__.py +17 -0
- pyfect/ai/typesafe/_errors.py +120 -0
- pyfect/ai/typesafe/client.py +146 -0
- pyfect/ai/typesafe/config.py +34 -0
- pyfect/ai/typesafe/decision_model.py +90 -0
- pyfect/ai/typesafe/schema.py +140 -0
- pyfect/batching/__init__.py +58 -0
- pyfect/batching/_api.py +71 -0
- pyfect/batching/_engine.py +229 -0
- pyfect/batching/_from_effect.py +76 -0
- pyfect/batching/_persisted.py +98 -0
- pyfect/batching/_request.py +133 -0
- pyfect/batching/_resolver.py +477 -0
- pyfect/batching/_with_cache.py +90 -0
- pyfect/big_decimal.py +500 -0
- pyfect/brand.py +114 -0
- pyfect/byte_size.py +354 -0
- pyfect/cache/__init__.py +29 -0
- pyfect/cache/_cache.py +426 -0
- pyfect/cache/_expiry.py +69 -0
- pyfect/cache/_scoped_cache.py +546 -0
- pyfect/case.py +174 -0
- pyfect/cause.py +462 -0
- pyfect/channel/__init__.py +155 -0
- pyfect/channel/_base.py +92 -0
- pyfect/channel/_catch.py +158 -0
- pyfect/channel/_catch_reason.py +572 -0
- pyfect/channel/_channel.py +158 -0
- pyfect/channel/_combinators.py +220 -0
- pyfect/channel/_concurrency_methods.py +217 -0
- pyfect/channel/_concurrent_map.py +118 -0
- pyfect/channel/_constructors.py +349 -0
- pyfect/channel/_context.py +232 -0
- pyfect/channel/_error_methods.py +336 -0
- pyfect/channel/_errors.py +180 -0
- pyfect/channel/_filters.py +225 -0
- pyfect/channel/_lifecycle.py +71 -0
- pyfect/channel/_lifecycle_methods.py +206 -0
- pyfect/channel/_loop.py +100 -0
- pyfect/channel/_merge.py +246 -0
- pyfect/channel/_repeat.py +154 -0
- pyfect/channel/_run.py +149 -0
- pyfect/channel/_run_methods.py +341 -0
- pyfect/channel/_sinks.py +88 -0
- pyfect/channel/_sources.py +238 -0
- pyfect/channel/_text.py +176 -0
- pyfect/channel/_transform_methods.py +415 -0
- pyfect/channel/_upstream.py +67 -0
- pyfect/channel_schema.py +131 -0
- pyfect/cli/__init__.py +79 -0
- pyfect/cli/_ansi.py +82 -0
- pyfect/cli/_command.py +469 -0
- pyfect/cli/_config.py +164 -0
- pyfect/cli/_environment.py +10 -0
- pyfect/cli/_help.py +195 -0
- pyfect/cli/_lexer.py +81 -0
- pyfect/cli/_parser.py +465 -0
- pyfect/cli/_settings.py +85 -0
- pyfect/cli/_suggest.py +33 -0
- pyfect/cli/_width.py +134 -0
- pyfect/cli/_wizard.py +317 -0
- pyfect/cli/argument.py +212 -0
- pyfect/cli/cli_config.py +40 -0
- pyfect/cli/cli_error.py +242 -0
- pyfect/cli/cli_output.py +305 -0
- pyfect/cli/command.py +440 -0
- pyfect/cli/completions/__init__.py +50 -0
- pyfect/cli/completions/_completer.ps1 +190 -0
- pyfect/cli/completions/_descriptor.py +182 -0
- pyfect/cli/completions/bash.py +300 -0
- pyfect/cli/completions/fish.py +164 -0
- pyfect/cli/completions/powershell.py +156 -0
- pyfect/cli/completions/zsh.py +189 -0
- pyfect/cli/flag.py +246 -0
- pyfect/cli/global_flag.py +99 -0
- pyfect/cli/help_doc.py +63 -0
- pyfect/cli/param.py +789 -0
- pyfect/cli/primitive.py +308 -0
- pyfect/cli/prompt/__init__.py +75 -0
- pyfect/cli/prompt/_auto_complete.py +205 -0
- pyfect/cli/prompt/_choices.py +104 -0
- pyfect/cli/prompt/_confirm.py +195 -0
- pyfect/cli/prompt/_core.py +512 -0
- pyfect/cli/prompt/_date.py +173 -0
- pyfect/cli/prompt/_date_parts.py +387 -0
- pyfect/cli/prompt/_file.py +372 -0
- pyfect/cli/prompt/_multi_select.py +204 -0
- pyfect/cli/prompt/_number.py +257 -0
- pyfect/cli/prompt/_select.py +117 -0
- pyfect/cli/prompt/_text.py +239 -0
- pyfect/cli/terminal_capabilities.py +118 -0
- pyfect/clock.py +150 -0
- pyfect/cluster/__init__.py +155 -0
- pyfect/cluster/_cluster_abandon.py +36 -0
- pyfect/cluster/_cluster_layer.py +122 -0
- pyfect/cluster/_entity_activation.py +16 -0
- pyfect/cluster/_entity_manager/__init__.py +22 -0
- pyfect/cluster/_entity_manager/_activation.py +540 -0
- pyfect/cluster/_entity_manager/_core.py +186 -0
- pyfect/cluster/_entity_manager/_decode.py +60 -0
- pyfect/cluster/_entity_manager/_manager.py +423 -0
- pyfect/cluster/_entity_manager/_state.py +113 -0
- pyfect/cluster/_entity_reaper.py +94 -0
- pyfect/cluster/_hash.py +13 -0
- pyfect/cluster/_interruptors.py +88 -0
- pyfect/cluster/_resource_map.py +179 -0
- pyfect/cluster/_resource_ref.py +266 -0
- pyfect/cluster/_shard_lock.py +14 -0
- pyfect/cluster/cluster_cron.py +230 -0
- pyfect/cluster/cluster_error.py +81 -0
- pyfect/cluster/cluster_http.py +210 -0
- pyfect/cluster/cluster_metrics.py +54 -0
- pyfect/cluster/cluster_schema.py +137 -0
- pyfect/cluster/cluster_socket.py +197 -0
- pyfect/cluster/cluster_workflow_engine/__init__.py +24 -0
- pyfect/cluster/cluster_workflow_engine/_engine.py +1099 -0
- pyfect/cluster/cluster_workflow_engine/_entities.py +298 -0
- pyfect/cluster/deliver_at.py +31 -0
- pyfect/cluster/entity/__init__.py +39 -0
- pyfect/cluster/entity/_entity.py +332 -0
- pyfect/cluster/entity/_handlers.py +168 -0
- pyfect/cluster/entity/_keep_alive.py +74 -0
- pyfect/cluster/entity/_queue.py +103 -0
- pyfect/cluster/entity/_request.py +34 -0
- pyfect/cluster/entity/_services.py +23 -0
- pyfect/cluster/entity/_test_client.py +237 -0
- pyfect/cluster/entity_address.py +25 -0
- pyfect/cluster/entity_id.py +20 -0
- pyfect/cluster/entity_proxy.py +161 -0
- pyfect/cluster/entity_proxy_server.py +103 -0
- pyfect/cluster/entity_resource.py +122 -0
- pyfect/cluster/entity_type.py +20 -0
- pyfect/cluster/envelope.py +307 -0
- pyfect/cluster/http_runner.py +254 -0
- pyfect/cluster/k8s_http_client.py +281 -0
- pyfect/cluster/k8s_types.py +1394 -0
- pyfect/cluster/machine_id.py +21 -0
- pyfect/cluster/message.py +219 -0
- pyfect/cluster/message_storage/__init__.py +67 -0
- pyfect/cluster/message_storage/_encoded.py +393 -0
- pyfect/cluster/message_storage/_memory.py +386 -0
- pyfect/cluster/message_storage/_noop.py +109 -0
- pyfect/cluster/message_storage/_service.py +388 -0
- pyfect/cluster/reply.py +236 -0
- pyfect/cluster/runner.py +32 -0
- pyfect/cluster/runner_address.py +23 -0
- pyfect/cluster/runner_health.py +93 -0
- pyfect/cluster/runner_server.py +286 -0
- pyfect/cluster/runner_storage.py +212 -0
- pyfect/cluster/runners/__init__.py +55 -0
- pyfect/cluster/runners/_local.py +460 -0
- pyfect/cluster/runners/_noop.py +57 -0
- pyfect/cluster/runners/_rpc.py +290 -0
- pyfect/cluster/runners/_service.py +113 -0
- pyfect/cluster/shard_id.py +39 -0
- pyfect/cluster/sharding/__init__.py +12 -0
- pyfect/cluster/sharding/_acquisition.py +490 -0
- pyfect/cluster/sharding/_clients.py +271 -0
- pyfect/cluster/sharding/_make.py +388 -0
- pyfect/cluster/sharding/_routing.py +296 -0
- pyfect/cluster/sharding/_runners_sync.py +198 -0
- pyfect/cluster/sharding/_service.py +91 -0
- pyfect/cluster/sharding/_singletons.py +183 -0
- pyfect/cluster/sharding/_state.py +296 -0
- pyfect/cluster/sharding/_storage_loop.py +421 -0
- pyfect/cluster/sharding_config.py +230 -0
- pyfect/cluster/sharding_registration_event.py +28 -0
- pyfect/cluster/single_runner.py +49 -0
- pyfect/cluster/singleton.py +36 -0
- pyfect/cluster/singleton_address.py +20 -0
- pyfect/cluster/snowflake.py +133 -0
- pyfect/cluster/socket_runner.py +67 -0
- pyfect/cluster/sql_message_storage.py +1001 -0
- pyfect/cluster/sql_runner_storage.py +841 -0
- pyfect/cluster/test_runner.py +23 -0
- pyfect/config/__init__.py +93 -0
- pyfect/config/_all.py +96 -0
- pyfect/config/_collections.py +291 -0
- pyfect/config/_config.py +261 -0
- pyfect/config/_constructors.py +106 -0
- pyfect/config/_error.py +27 -0
- pyfect/config/_leaf.py +92 -0
- pyfect/config/_scalars.py +193 -0
- pyfect/config/_schema.py +340 -0
- pyfect/config_provider/__init__.py +60 -0
- pyfect/config_provider/_dir.py +84 -0
- pyfect/config_provider/_dotenv.py +127 -0
- pyfect/config_provider/_env.py +79 -0
- pyfect/config_provider/_layers.py +80 -0
- pyfect/config_provider/_node.py +90 -0
- pyfect/config_provider/_provider.py +118 -0
- pyfect/config_provider/_unknown.py +62 -0
- pyfect/console/__init__.py +63 -0
- pyfect/console/_effects.py +134 -0
- pyfect/console/_live.py +156 -0
- pyfect/console/_service.py +49 -0
- pyfect/console/_table.py +90 -0
- pyfect/context.py +414 -0
- pyfect/cron/__init__.py +30 -0
- pyfect/cron/_model.py +183 -0
- pyfect/cron/_search.py +268 -0
- pyfect/cron/_text.py +202 -0
- pyfect/cron/_wall.py +42 -0
- pyfect/crypto.py +257 -0
- pyfect/date_time/__init__.py +77 -0
- pyfect/date_time/_calendar.py +209 -0
- pyfect/date_time/_current.py +118 -0
- pyfect/date_time/_epoch.py +22 -0
- pyfect/date_time/_iso.py +80 -0
- pyfect/date_time/_zones.py +135 -0
- pyfect/date_time/intl.py +144 -0
- pyfect/deferred.py +145 -0
- pyfect/devtools/__init__.py +24 -0
- pyfect/devtools/client.py +323 -0
- pyfect/devtools/layers.py +32 -0
- pyfect/devtools/schema.py +301 -0
- pyfect/devtools/server.py +72 -0
- pyfect/duration.py +295 -0
- pyfect/effect.py +565 -0
- pyfect/effectable.py +37 -0
- pyfect/encoding/__init__.py +62 -0
- pyfect/encoding/_archive.py +197 -0
- pyfect/encoding/_json_array_scanner.py +477 -0
- pyfect/encoding/base64.py +23 -0
- pyfect/encoding/base64_url.py +19 -0
- pyfect/encoding/encoding_error.py +17 -0
- pyfect/encoding/hex.py +28 -0
- pyfect/encoding/ini.py +188 -0
- pyfect/encoding/json_array.py +246 -0
- pyfect/encoding/ndjson.py +461 -0
- pyfect/encoding/schema_binary/__init__.py +257 -0
- pyfect/encoding/schema_binary/_channels.py +173 -0
- pyfect/encoding/schema_binary/_compile.py +584 -0
- pyfect/encoding/schema_binary/_decode.py +848 -0
- pyfect/encoding/schema_binary/_dictionary.py +90 -0
- pyfect/encoding/schema_binary/_encode.py +838 -0
- pyfect/encoding/schema_binary/_fingerprint.py +168 -0
- pyfect/encoding/schema_binary/_layout.py +350 -0
- pyfect/encoding/schema_binary/_literals.py +47 -0
- pyfect/encoding/schema_binary/_parser.py +163 -0
- pyfect/encoding/schema_binary/_reader.py +148 -0
- pyfect/encoding/schema_binary/_target.py +310 -0
- pyfect/encoding/schema_binary/_values.py +133 -0
- pyfect/encoding/schema_binary/_wire.py +253 -0
- pyfect/encoding/sse.py +476 -0
- pyfect/encoding/tar/__init__.py +396 -0
- pyfect/encoding/tar/_decoder.py +220 -0
- pyfect/encoding/tar/_encoder.py +83 -0
- pyfect/encoding/tar/_header.py +494 -0
- pyfect/encoding/tar/_model.py +82 -0
- pyfect/encoding/toml.py +375 -0
- pyfect/encoding/xml/__init__.py +222 -0
- pyfect/encoding/xml/_chars.py +119 -0
- pyfect/encoding/xml/_collector.py +106 -0
- pyfect/encoding/xml/_inputs.py +126 -0
- pyfect/encoding/xml/_model.py +114 -0
- pyfect/encoding/xml/_parser.py +694 -0
- pyfect/encoding/xml/_scanner.py +180 -0
- pyfect/encoding/xml/_writer.py +238 -0
- pyfect/encoding/yaml.py +739 -0
- pyfect/encoding/zip/__init__.py +392 -0
- pyfect/encoding/zip/_decoder.py +378 -0
- pyfect/encoding/zip/_format.py +122 -0
- pyfect/encoding/zip/_inflate.py +65 -0
- pyfect/encoding/zip/_model.py +86 -0
- pyfect/encoding/zip/_writer.py +311 -0
- pyfect/error_reporter.py +240 -0
- pyfect/errors.py +321 -0
- pyfect/eventlog/__init__.py +77 -0
- pyfect/eventlog/_conflicts.py +94 -0
- pyfect/eventlog/_crypto.py +64 -0
- pyfect/eventlog/_identity.py +26 -0
- pyfect/eventlog/_identity_derivation.py +79 -0
- pyfect/eventlog/_transferable_bytes.py +55 -0
- pyfect/eventlog/_uuid.py +33 -0
- pyfect/eventlog/event.py +115 -0
- pyfect/eventlog/event_group.py +55 -0
- pyfect/eventlog/event_journal.py +630 -0
- pyfect/eventlog/event_log.py +1039 -0
- pyfect/eventlog/event_log_encryption.py +219 -0
- pyfect/eventlog/event_log_message.py +357 -0
- pyfect/eventlog/event_log_remote.py +465 -0
- pyfect/eventlog/event_log_server.py +442 -0
- pyfect/eventlog/event_log_server_encrypted.py +322 -0
- pyfect/eventlog/event_log_server_unencrypted.py +814 -0
- pyfect/eventlog/event_log_session_auth.py +250 -0
- pyfect/eventlog/event_row.py +423 -0
- pyfect/eventlog/sql_event_journal.py +692 -0
- pyfect/eventlog/sql_event_log_server_encrypted.py +440 -0
- pyfect/eventlog/sql_event_log_server_unencrypted.py +568 -0
- pyfect/execution_plan/__init__.py +42 -0
- pyfect/execution_plan/_events.py +128 -0
- pyfect/execution_plan/_model.py +108 -0
- pyfect/execution_plan/_run.py +176 -0
- pyfect/exit.py +205 -0
- pyfect/fiber_handle.py +305 -0
- pyfect/fiber_map.py +350 -0
- pyfect/fiber_set.py +232 -0
- pyfect/file_system/__init__.py +69 -0
- pyfect/file_system/_copy.py +116 -0
- pyfect/file_system/_errors.py +32 -0
- pyfect/file_system/_file.py +177 -0
- pyfect/file_system/_live.py +351 -0
- pyfect/file_system/_model.py +72 -0
- pyfect/file_system/_noop.py +189 -0
- pyfect/file_system/_service.py +336 -0
- pyfect/file_system/_stat.py +69 -0
- pyfect/file_system/_watch.py +107 -0
- pyfect/filters.py +245 -0
- pyfect/graph/__init__.py +101 -0
- pyfect/graph/_all_pairs.py +150 -0
- pyfect/graph/_combine.py +259 -0
- pyfect/graph/_compact.py +143 -0
- pyfect/graph/_components.py +200 -0
- pyfect/graph/_cycles.py +144 -0
- pyfect/graph/_enumerate.py +168 -0
- pyfect/graph/_errors.py +51 -0
- pyfect/graph/_flow.py +164 -0
- pyfect/graph/_forest.py +109 -0
- pyfect/graph/_graph.py +269 -0
- pyfect/graph/_lowlink.py +98 -0
- pyfect/graph/_matching.py +101 -0
- pyfect/graph/_mutate.py +211 -0
- pyfect/graph/_query.py +138 -0
- pyfect/graph/_records.py +119 -0
- pyfect/graph/_render.py +109 -0
- pyfect/graph/_shortest.py +274 -0
- pyfect/graph/_snapshot.py +50 -0
- pyfect/graph/_store.py +136 -0
- pyfect/graph/_traverse.py +289 -0
- pyfect/graph/_view.py +384 -0
- pyfect/graph/_walker.py +70 -0
- pyfect/graphql/__init__.py +87 -0
- pyfect/graphql/_codec.py +117 -0
- pyfect/graphql/_http.py +309 -0
- pyfect/graphql/_schemas.py +39 -0
- pyfect/graphql/_slices.py +110 -0
- pyfect/graphql/_template.py +111 -0
- pyfect/graphql/_types.py +115 -0
- pyfect/graphql/_ws.py +395 -0
- pyfect/graphql/_ws_messages.py +120 -0
- pyfect/graphql/client.py +428 -0
- pyfect/graphql/client_error.py +285 -0
- pyfect/graphql/group.py +142 -0
- pyfect/graphql/middleware.py +98 -0
- pyfect/graphql/operation.py +409 -0
- pyfect/graphql/protocol.py +119 -0
- pyfect/graphql/resolver.py +574 -0
- pyfect/graphql/template.py +61 -0
- pyfect/hash_map.py +317 -0
- pyfect/hash_ring.py +206 -0
- pyfect/hash_set.py +163 -0
- pyfect/http/__init__.py +125 -0
- pyfect/http/_asgi_listener.py +272 -0
- pyfect/http/_authority.py +63 -0
- pyfect/http/_brotli.py +27 -0
- pyfect/http/_compression.py +52 -0
- pyfect/http/_field.py +18 -0
- pyfect/http/_foreign_path.py +27 -0
- pyfect/http/_form_data.py +34 -0
- pyfect/http/_httpx_options.py +120 -0
- pyfect/http/_in_memory.py +76 -0
- pyfect/http/_request_state.py +59 -0
- pyfect/http/_vary.py +14 -0
- pyfect/http/abort.py +65 -0
- pyfect/http/asgi/__init__.py +59 -0
- pyfect/http/asgi/_app.py +155 -0
- pyfect/http/asgi/_http.py +201 -0
- pyfect/http/asgi/_mount.py +263 -0
- pyfect/http/asgi/_scope.py +86 -0
- pyfect/http/asgi/_serve.py +72 -0
- pyfect/http/asgi/_types.py +13 -0
- pyfect/http/asgi/_web_socket.py +325 -0
- pyfect/http/body.py +437 -0
- pyfect/http/client/__init__.py +111 -0
- pyfect/http/client/_circuit_breaker.py +113 -0
- pyfect/http/client/_cookie_jar.py +120 -0
- pyfect/http/client/_make.py +252 -0
- pyfect/http/client/_rate_limit.py +423 -0
- pyfect/http/client/_redirects.py +93 -0
- pyfect/http/client/_retry.py +115 -0
- pyfect/http/client/_service.py +497 -0
- pyfect/http/cookies.py +444 -0
- pyfect/http/error.py +188 -0
- pyfect/http/etag.py +96 -0
- pyfect/http/file_like.py +49 -0
- pyfect/http/find_my_way/__init__.py +10 -0
- pyfect/http/find_my_way/_nodes.py +186 -0
- pyfect/http/find_my_way/_paths.py +43 -0
- pyfect/http/find_my_way/_query_string.py +67 -0
- pyfect/http/find_my_way/_router.py +401 -0
- pyfect/http/headers.py +343 -0
- pyfect/http/http_effect.py +198 -0
- pyfect/http/httpx_client.py +291 -0
- pyfect/http/hypercorn_server.py +122 -0
- pyfect/http/incoming_message.py +140 -0
- pyfect/http/live_server/__init__.py +191 -0
- pyfect/http/live_server/_body.py +164 -0
- pyfect/http/live_server/_exchange.py +321 -0
- pyfect/http/live_server/_head.py +194 -0
- pyfect/http/live_server/_inbound.py +156 -0
- pyfect/http/live_server/_listener.py +211 -0
- pyfect/http/live_server/_serve.py +225 -0
- pyfect/http/live_server/_timers.py +20 -0
- pyfect/http/live_server/_upgrade.py +108 -0
- pyfect/http/live_server/_websocket.py +286 -0
- pyfect/http/live_server/_ws_frames.py +242 -0
- pyfect/http/method.py +30 -0
- pyfect/http/middleware/__init__.py +61 -0
- pyfect/http/middleware/_compression.py +156 -0
- pyfect/http/middleware/_cors.py +129 -0
- pyfect/http/middleware/_decompression.py +274 -0
- pyfect/http/middleware/_forwarded.py +113 -0
- pyfect/http/middleware/_logger.py +73 -0
- pyfect/http/middleware/_search_params.py +34 -0
- pyfect/http/middleware/_tracer.py +153 -0
- pyfect/http/middleware/_types.py +16 -0
- pyfect/http/mime/__init__.py +69 -0
- pyfect/http/mime/_types.py +361 -0
- pyfect/http/multipart.py +613 -0
- pyfect/http/multipart_parser/__init__.py +348 -0
- pyfect/http/multipart_parser/_content_type.py +109 -0
- pyfect/http/multipart_parser/_headers.py +206 -0
- pyfect/http/multipart_parser/_search.py +85 -0
- pyfect/http/origin.py +146 -0
- pyfect/http/platform.py +593 -0
- pyfect/http/prometheus.py +34 -0
- pyfect/http/request.py +384 -0
- pyfect/http/response.py +474 -0
- pyfect/http/router/__init__.py +82 -0
- pyfect/http/router/_api.py +462 -0
- pyfect/http/router/_in_memory.py +76 -0
- pyfect/http/router/_markers.py +47 -0
- pyfect/http/router/_middleware.py +181 -0
- pyfect/http/router/_middleware_stack.py +51 -0
- pyfect/http/router/_provided.py +31 -0
- pyfect/http/router/_route.py +114 -0
- pyfect/http/router/_router.py +308 -0
- pyfect/http/schema.py +276 -0
- pyfect/http/server.py +199 -0
- pyfect/http/server_error.py +261 -0
- pyfect/http/server_request.py +729 -0
- pyfect/http/server_respondable.py +77 -0
- pyfect/http/server_response.py +569 -0
- pyfect/http/static_server/__init__.py +257 -0
- pyfect/http/static_server/_conditional.py +72 -0
- pyfect/http/static_server/_mime.py +55 -0
- pyfect/http/static_server/_range.py +53 -0
- pyfect/http/status.py +51 -0
- pyfect/http/template.py +161 -0
- pyfect/http/trace_context.py +91 -0
- pyfect/http/url.py +442 -0
- pyfect/http/url_params.py +304 -0
- pyfect/http/uvicorn_server.py +119 -0
- pyfect/http/web.py +171 -0
- pyfect/http/web_handler.py +463 -0
- pyfect/http/wire.py +209 -0
- pyfect/http_api/__init__.py +83 -0
- pyfect/http_api/_assets/scalar.js +8 -0
- pyfect/http_api/_assets/swagger-ui.css +3 -0
- pyfect/http_api/_assets/swagger-ui.js +4 -0
- pyfect/http_api/_client_method.py +220 -0
- pyfect/http_api/_client_payload.py +144 -0
- pyfect/http_api/_client_response.py +377 -0
- pyfect/http_api/_handlers.py +282 -0
- pyfect/http_api/_html.py +38 -0
- pyfect/http_api/_media_type.py +6 -0
- pyfect/http_api/_openapi_responses.py +164 -0
- pyfect/http_api/_openapi_schemas.py +163 -0
- pyfect/http_api/_path.py +102 -0
- pyfect/http_api/_payload.py +260 -0
- pyfect/http_api/_response.py +363 -0
- pyfect/http_api/_security.py +174 -0
- pyfect/http_api/api.py +265 -0
- pyfect/http_api/builder.py +447 -0
- pyfect/http_api/client.py +474 -0
- pyfect/http_api/endpoint.py +969 -0
- pyfect/http_api/error.py +280 -0
- pyfect/http_api/group.py +215 -0
- pyfect/http_api/middleware.py +232 -0
- pyfect/http_api/openapi.py +396 -0
- pyfect/http_api/scalar.py +143 -0
- pyfect/http_api/schema.py +713 -0
- pyfect/http_api/security.py +91 -0
- pyfect/http_api/swagger.py +69 -0
- pyfect/http_api/test.py +105 -0
- pyfect/json_patch.py +255 -0
- pyfect/json_pointer.py +100 -0
- pyfect/json_schema/__init__.py +66 -0
- pyfect/json_schema/_api.py +96 -0
- pyfect/json_schema/_converter.py +258 -0
- pyfect/json_schema/_draft04.py +198 -0
- pyfect/json_schema/_draft07.py +203 -0
- pyfect/json_schema/_keywords.py +202 -0
- pyfect/json_schema/_model.py +201 -0
- pyfect/json_schema/_openapi.py +208 -0
- pyfect/latch.py +127 -0
- pyfect/layer.py +479 -0
- pyfect/layer_map.py +279 -0
- pyfect/layer_ref.py +208 -0
- pyfect/logger/__init__.py +91 -0
- pyfect/logger/_batched.py +49 -0
- pyfect/logger/_console.py +116 -0
- pyfect/logger/_file.py +31 -0
- pyfect/logger/_formats.py +104 -0
- pyfect/logger/_layer.py +50 -0
- pyfect/logger/_level.py +53 -0
- pyfect/logger/_log.py +142 -0
- pyfect/logger/_model.py +163 -0
- pyfect/logger/_refs.py +78 -0
- pyfect/logger/_stdlib.py +54 -0
- pyfect/logger/_text.py +263 -0
- pyfect/logger/_tracer.py +35 -0
- pyfect/managed_runtime.py +393 -0
- pyfect/metric/__init__.py +119 -0
- pyfect/metric/_boundaries.py +27 -0
- pyfect/metric/_kinds.py +417 -0
- pyfect/metric/_metric.py +302 -0
- pyfect/metric/_prometheus.py +150 -0
- pyfect/metric/_refs.py +78 -0
- pyfect/metric/_runtime.py +75 -0
- pyfect/metric/_snapshot.py +97 -0
- pyfect/metric/_state.py +74 -0
- pyfect/msgspec.py +98 -0
- pyfect/mutable_hash_map.py +140 -0
- pyfect/mutable_hash_set.py +95 -0
- pyfect/net/__init__.py +18 -0
- pyfect/net/_errors.py +29 -0
- pyfect/net/address_resolver.py +179 -0
- pyfect/net/dns/__init__.py +92 -0
- pyfect/net/dns/_error.py +43 -0
- pyfect/net/dns/_records.py +335 -0
- pyfect/net/dns/_service.py +287 -0
- pyfect/net/dns/_system.py +89 -0
- pyfect/net/host.py +311 -0
- pyfect/net/ip_interface.py +202 -0
- pyfect/net/ip_network.py +250 -0
- pyfect/net/net_address/__init__.py +245 -0
- pyfect/net/net_address/_inet.py +412 -0
- pyfect/net/net_address/_ip.py +348 -0
- pyfect/net/net_address/_mac.py +119 -0
- pyfect/optic/__init__.py +50 -0
- pyfect/optic/_builtins.py +90 -0
- pyfect/optic/_data.py +97 -0
- pyfect/optic/_optic.py +422 -0
- pyfect/optic/_steps.py +151 -0
- pyfect/option.py +362 -0
- pyfect/otel/__init__.py +22 -0
- pyfect/otel/_attributes.py +67 -0
- pyfect/otel/_producer.py +322 -0
- pyfect/otel/_shutdown.py +26 -0
- pyfect/otel/_span.py +292 -0
- pyfect/otel/_wrapper.py +92 -0
- pyfect/otel/logger.py +154 -0
- pyfect/otel/metrics.py +126 -0
- pyfect/otel/resource.py +111 -0
- pyfect/otel/sdk.py +195 -0
- pyfect/otel/tracer.py +157 -0
- pyfect/partitioned_semaphore.py +211 -0
- pyfect/path.py +304 -0
- pyfect/persistence/__init__.py +77 -0
- pyfect/persistence/_columns.py +32 -0
- pyfect/persistence/_error.py +14 -0
- pyfect/persistence/_seconds.py +68 -0
- pyfect/persistence/_sql_keys.py +47 -0
- pyfect/persistence/circuit_breaker/__init__.py +120 -0
- pyfect/persistence/circuit_breaker/_breaker.py +317 -0
- pyfect/persistence/circuit_breaker/_errors.py +67 -0
- pyfect/persistence/circuit_breaker/_memory.py +243 -0
- pyfect/persistence/circuit_breaker/_redis.py +509 -0
- pyfect/persistence/circuit_breaker/_store.py +60 -0
- pyfect/persistence/circuit_breaker/_types.py +191 -0
- pyfect/persistence/key_value_store/__init__.py +51 -0
- pyfect/persistence/key_value_store/_layers.py +216 -0
- pyfect/persistence/key_value_store/_schema_store.py +91 -0
- pyfect/persistence/key_value_store/_sql.py +197 -0
- pyfect/persistence/key_value_store/_store.py +212 -0
- pyfect/persistence/persistable.py +164 -0
- pyfect/persistence/persisted_cache.py +159 -0
- pyfect/persistence/persisted_queue/__init__.py +75 -0
- pyfect/persistence/persisted_queue/_error.py +42 -0
- pyfect/persistence/persisted_queue/_mailbox.py +101 -0
- pyfect/persistence/persisted_queue/_memory.py +182 -0
- pyfect/persistence/persisted_queue/_queue.py +272 -0
- pyfect/persistence/persisted_queue/_redis.py +455 -0
- pyfect/persistence/persisted_queue/_redis_scripts.py +395 -0
- pyfect/persistence/persisted_queue/_sql.py +334 -0
- pyfect/persistence/persisted_queue/_sql_migrations.py +319 -0
- pyfect/persistence/persisted_queue/_sql_statements.py +441 -0
- pyfect/persistence/persisted_queue/_store.py +147 -0
- pyfect/persistence/persistence/__init__.py +84 -0
- pyfect/persistence/persistence/_kvs.py +135 -0
- pyfect/persistence/persistence/_memory.py +62 -0
- pyfect/persistence/persistence/_redis.py +162 -0
- pyfect/persistence/persistence/_service.py +276 -0
- pyfect/persistence/persistence/_sql.py +499 -0
- pyfect/persistence/rate_limiter/__init__.py +82 -0
- pyfect/persistence/rate_limiter/_errors.py +59 -0
- pyfect/persistence/rate_limiter/_limiter.py +325 -0
- pyfect/persistence/rate_limiter/_memory.py +217 -0
- pyfect/persistence/rate_limiter/_redis.py +485 -0
- pyfect/persistence/rate_limiter/_store.py +89 -0
- pyfect/persistence/redis/__init__.py +52 -0
- pyfect/persistence/redis/_error.py +12 -0
- pyfect/persistence/redis/_make.py +234 -0
- pyfect/persistence/redis/_script.py +78 -0
- pyfect/persistence/redis/_service.py +104 -0
- pyfect/persistence/redis_py.py +303 -0
- pyfect/persistence/user_directories.py +195 -0
- pyfect/platform.py +26 -0
- pyfect/platform_error.py +256 -0
- pyfect/pool/__init__.py +36 -0
- pyfect/pool/_pool.py +707 -0
- pyfect/pool/_state.py +161 -0
- pyfect/pool/_ttl.py +105 -0
- pyfect/pool/_waiters.py +116 -0
- pyfect/primary_key.py +35 -0
- pyfect/process/__init__.py +82 -0
- pyfect/process/_child.py +213 -0
- pyfect/process/_command.py +358 -0
- pyfect/process/_handle.py +77 -0
- pyfect/process/_live.py +455 -0
- pyfect/process/_os.py +78 -0
- pyfect/process/_pipes.py +291 -0
- pyfect/process/_spawner.py +75 -0
- pyfect/pubsub/__init__.py +33 -0
- pyfect/pubsub/_atomic.py +181 -0
- pyfect/pubsub/_hub.py +214 -0
- pyfect/pubsub/_pubsub.py +214 -0
- pyfect/pubsub/_replay.py +107 -0
- pyfect/pubsub/_subscription.py +226 -0
- pyfect/pull.py +132 -0
- pyfect/py.typed +0 -0
- pyfect/pydantic.py +188 -0
- pyfect/queue/__init__.py +33 -0
- pyfect/queue/_queue.py +525 -0
- pyfect/queue/_state.py +111 -0
- pyfect/queue/_views.py +55 -0
- pyfect/random.py +146 -0
- pyfect/rc_map.py +363 -0
- pyfect/rc_ref.py +271 -0
- pyfect/reactivity/__init__.py +53 -0
- pyfect/reactivity/_async_result_schema.py +227 -0
- pyfect/reactivity/_hydration.py +18 -0
- pyfect/reactivity/_live.py +21 -0
- pyfect/reactivity/_reactivity.py +256 -0
- pyfect/reactivity/_result_effect.py +17 -0
- pyfect/reactivity/_time.py +11 -0
- pyfect/reactivity/async_result.py +679 -0
- pyfect/reactivity/atom/__init__.py +143 -0
- pyfect/reactivity/atom/_combinators.py +223 -0
- pyfect/reactivity/atom/_constructors.py +80 -0
- pyfect/reactivity/atom/_conversions.py +79 -0
- pyfect/reactivity/atom/_effect.py +252 -0
- pyfect/reactivity/atom/_fn.py +341 -0
- pyfect/reactivity/atom/_kvs.py +101 -0
- pyfect/reactivity/atom/_optimistic.py +162 -0
- pyfect/reactivity/atom/_runtime.py +323 -0
- pyfect/reactivity/atom/_serializable.py +65 -0
- pyfect/reactivity/atom/_subscription_ref.py +102 -0
- pyfect/reactivity/atom/_swr.py +69 -0
- pyfect/reactivity/atom/_types.py +435 -0
- pyfect/reactivity/atom_http_api.py +667 -0
- pyfect/reactivity/atom_ref.py +260 -0
- pyfect/reactivity/atom_registry/__init__.py +219 -0
- pyfect/reactivity/atom_registry/_batch.py +96 -0
- pyfect/reactivity/atom_registry/_dispatcher.py +60 -0
- pyfect/reactivity/atom_registry/_lifetime.py +202 -0
- pyfect/reactivity/atom_registry/_node.py +423 -0
- pyfect/reactivity/atom_registry/_registry.py +380 -0
- pyfect/reactivity/atom_rpc.py +283 -0
- pyfect/reactivity/hydration.py +200 -0
- pyfect/redacted.py +83 -0
- pyfect/ref.py +129 -0
- pyfect/resource.py +98 -0
- pyfect/result.py +306 -0
- pyfect/rpc/__init__.py +64 -0
- pyfect/rpc/_encoded_schema.py +86 -0
- pyfect/rpc/_json_rpc.py +246 -0
- pyfect/rpc/_methods.py +177 -0
- pyfect/rpc/_rpc.py +498 -0
- pyfect/rpc/client/__init__.py +100 -0
- pyfect/rpc/client/_core.py +449 -0
- pyfect/rpc/client/_http.py +257 -0
- pyfect/rpc/client/_protocol.py +171 -0
- pyfect/rpc/client/_serialized.py +321 -0
- pyfect/rpc/client/_transports.py +328 -0
- pyfect/rpc/client/_worker.py +283 -0
- pyfect/rpc/client_error.py +35 -0
- pyfect/rpc/group.py +296 -0
- pyfect/rpc/message.py +299 -0
- pyfect/rpc/middleware.py +182 -0
- pyfect/rpc/schema.py +89 -0
- pyfect/rpc/serialization.py +366 -0
- pyfect/rpc/server/__init__.py +181 -0
- pyfect/rpc/server/_core.py +549 -0
- pyfect/rpc/server/_protocol.py +123 -0
- pyfect/rpc/server/_serialized.py +363 -0
- pyfect/rpc/server/_stdio.py +101 -0
- pyfect/rpc/server/_transports.py +427 -0
- pyfect/rpc/server/_worker.py +108 -0
- pyfect/rpc/test.py +74 -0
- pyfect/rpc/worker.py +92 -0
- pyfect/run.py +393 -0
- pyfect/schedule/__init__.py +91 -0
- pyfect/schedule/_combinators.py +168 -0
- pyfect/schedule/_constructors.py +223 -0
- pyfect/schedule/_cron.py +49 -0
- pyfect/schedule/_drive.py +191 -0
- pyfect/schedule/_model.py +405 -0
- pyfect/scheduler.py +575 -0
- pyfect/schema/__init__.py +866 -0
- pyfect/schema/_adapters.py +39 -0
- pyfect/schema/_arbitrary_constraint.py +242 -0
- pyfect/schema/_arbitrary_graph.py +325 -0
- pyfect/schema/_arbitrary_links.py +179 -0
- pyfect/schema/_arbitrary_number.py +112 -0
- pyfect/schema/_arrays.py +287 -0
- pyfect/schema/_assignable.py +118 -0
- pyfect/schema/_boolean_literals.py +31 -0
- pyfect/schema/_checks_number.py +216 -0
- pyfect/schema/_checks_order.py +140 -0
- pyfect/schema/_checks_size.py +277 -0
- pyfect/schema/_checks_string.py +241 -0
- pyfect/schema/_class.py +569 -0
- pyfect/schema/_codec_derivation.py +227 -0
- pyfect/schema/_codec_links.py +221 -0
- pyfect/schema/_codecs.py +316 -0
- pyfect/schema/_collections.py +172 -0
- pyfect/schema/_constructor_default.py +89 -0
- pyfect/schema/_data.py +440 -0
- pyfect/schema/_data_codecs.py +145 -0
- pyfect/schema/_decimal_regexp.py +243 -0
- pyfect/schema/_declare.py +68 -0
- pyfect/schema/_derive.py +292 -0
- pyfect/schema/_duration_codecs.py +27 -0
- pyfect/schema/_ecma_pattern.py +416 -0
- pyfect/schema/_error.py +26 -0
- pyfect/schema/_error_instance.py +222 -0
- pyfect/schema/_graph.py +271 -0
- pyfect/schema/_instances.py +240 -0
- pyfect/schema/_jsonschema/__init__.py +90 -0
- pyfect/schema/_jsonschema/_annotations.py +114 -0
- pyfect/schema/_jsonschema/_checks.py +253 -0
- pyfect/schema/_jsonschema/_compile.py +346 -0
- pyfect/schema/_jsonschema/_exact.py +19 -0
- pyfect/schema/_jsonschema/_merge.py +211 -0
- pyfect/schema/_jsonschema/_objects.py +231 -0
- pyfect/schema/_jsonschema/_references.py +221 -0
- pyfect/schema/_jsonschema/_template.py +69 -0
- pyfect/schema/_literals.py +147 -0
- pyfect/schema/_middleware.py +209 -0
- pyfect/schema/_net.py +522 -0
- pyfect/schema/_options.py +49 -0
- pyfect/schema/_parse.py +351 -0
- pyfect/schema/_parsed_text.py +37 -0
- pyfect/schema/_primitives.py +48 -0
- pyfect/schema/_record.py +41 -0
- pyfect/schema/_refine.py +81 -0
- pyfect/schema/_schema.py +158 -0
- pyfect/schema/_semver.py +144 -0
- pyfect/schema/_strategy/__init__.py +258 -0
- pyfect/schema/_strategy/_big_decimal.py +203 -0
- pyfect/schema/_strategy/_composites.py +119 -0
- pyfect/schema/_strategy/_leaves.py +94 -0
- pyfect/schema/_strategy/_net.py +133 -0
- pyfect/schema/_struct.py +87 -0
- pyfect/schema/_struct_keys.py +128 -0
- pyfect/schema/_tagged.py +254 -0
- pyfect/schema/_template_literal.py +172 -0
- pyfect/schema/_to_codec_array_from_single.py +115 -0
- pyfect/schema/_to_codec_go_json.py +174 -0
- pyfect/schema/_to_codec_json.py +73 -0
- pyfect/schema/_to_codec_string_tree.py +115 -0
- pyfect/schema/_to_differ_json_patch.py +44 -0
- pyfect/schema/_to_encoder_xml.py +119 -0
- pyfect/schema/_to_equivalence.py +186 -0
- pyfect/schema/_to_formatter.py +180 -0
- pyfect/schema/_to_iso.py +73 -0
- pyfect/schema/_to_strategy.py +31 -0
- pyfect/schema/_transform.py +210 -0
- pyfect/schema/_tree.py +27 -0
- pyfect/schema/_type_walk.py +53 -0
- pyfect/schema/_undefined.py +58 -0
- pyfect/schema/_value_codecs.py +84 -0
- pyfect/schema/_values.py +123 -0
- pyfect/schema/_void.py +86 -0
- pyfect/schema/ast/__init__.py +227 -0
- pyfect/schema/ast/_annotations.py +43 -0
- pyfect/schema/ast/_arrays.py +210 -0
- pyfect/schema/ast/_base.py +295 -0
- pyfect/schema/ast/_candidates.py +309 -0
- pyfect/schema/ast/_check.py +181 -0
- pyfect/schema/ast/_class.py +159 -0
- pyfect/schema/ast/_constructors.py +65 -0
- pyfect/schema/ast/_declaration.py +97 -0
- pyfect/schema/ast/_encoding.py +111 -0
- pyfect/schema/ast/_json.py +129 -0
- pyfect/schema/ast/_keywords.py +125 -0
- pyfect/schema/ast/_literal.py +157 -0
- pyfect/schema/ast/_objects.py +356 -0
- pyfect/schema/ast/_record.py +36 -0
- pyfect/schema/ast/_result.py +116 -0
- pyfect/schema/ast/_steps.py +209 -0
- pyfect/schema/ast/_suspend.py +65 -0
- pyfect/schema/ast/_template_literal.py +256 -0
- pyfect/schema/ast/_union.py +172 -0
- pyfect/schema/getter/__init__.py +136 -0
- pyfect/schema/getter/_binary.py +104 -0
- pyfect/schema/getter/_coerce.py +196 -0
- pyfect/schema/getter/_getter.py +243 -0
- pyfect/schema/getter/_make.py +151 -0
- pyfect/schema/getter/_text.py +141 -0
- pyfect/schema/getter/_tree_record.py +170 -0
- pyfect/schema/issue.py +420 -0
- pyfect/schema/model/__init__.py +120 -0
- pyfect/schema/model/_class.py +245 -0
- pyfect/schema/model/_dates.py +103 -0
- pyfect/schema/model/_fields.py +77 -0
- pyfect/schema/model/_toolkit.py +114 -0
- pyfect/schema/model/_uuid.py +69 -0
- pyfect/schema/representation/__init__.py +424 -0
- pyfect/schema/representation/_builtin_revivers.py +331 -0
- pyfect/schema/representation/_code.py +106 -0
- pyfect/schema/representation/_code_builtins.py +281 -0
- pyfect/schema/representation/_code_document.py +563 -0
- pyfect/schema/representation/_code_sort.py +111 -0
- pyfect/schema/representation/_from.py +278 -0
- pyfect/schema/representation/_import/__init__.py +78 -0
- pyfect/schema/representation/_import/_finalize.py +121 -0
- pyfect/schema/representation/_import/_intersect.py +467 -0
- pyfect/schema/representation/_import/_js_order.py +66 -0
- pyfect/schema/representation/_import/_keywords.py +224 -0
- pyfect/schema/representation/_import/_nodes.py +199 -0
- pyfect/schema/representation/_import/_translate.py +455 -0
- pyfect/schema/representation/_json.py +520 -0
- pyfect/schema/representation/_json_schema.py +178 -0
- pyfect/schema/representation/_model.py +284 -0
- pyfect/schema/representation/_revivers.py +89 -0
- pyfect/schema/representation/_to.py +249 -0
- pyfect/schema/transformation/__init__.py +99 -0
- pyfect/schema/transformation/_builtins.py +314 -0
- pyfect/schema/transformation/_durations.py +150 -0
- pyfect/schema/transformation/_transformation.py +177 -0
- pyfect/schema/variant_schema/__init__.py +69 -0
- pyfect/schema/variant_schema/_class.py +188 -0
- pyfect/schema/variant_schema/_defaults.py +30 -0
- pyfect/schema/variant_schema/_field.py +32 -0
- pyfect/schema/variant_schema/_struct.py +130 -0
- pyfect/schema/variant_schema/_toolkit.py +160 -0
- pyfect/scope.py +374 -0
- pyfect/scoped_ref.py +124 -0
- pyfect/semaphore.py +276 -0
- pyfect/semver.py +403 -0
- pyfect/semver_range.py +420 -0
- pyfect/sink/__init__.py +131 -0
- pyfect/sink/_combinators.py +151 -0
- pyfect/sink/_constructors.py +187 -0
- pyfect/sink/_context.py +87 -0
- pyfect/sink/_folds.py +336 -0
- pyfect/sink/_for_each.py +87 -0
- pyfect/sink/_sink.py +265 -0
- pyfect/sink/_steps.py +34 -0
- pyfect/sink/_taking.py +183 -0
- pyfect/socket/__init__.py +146 -0
- pyfect/socket/_accepted.py +65 -0
- pyfect/socket/_connection.py +384 -0
- pyfect/socket/_errors.py +135 -0
- pyfect/socket/_loop.py +66 -0
- pyfect/socket/_net.py +281 -0
- pyfect/socket/_socket.py +245 -0
- pyfect/socket/_tls.py +88 -0
- pyfect/socket/_tls_session.py +117 -0
- pyfect/socket/_web_socket.py +555 -0
- pyfect/socket/datagram/__init__.py +110 -0
- pyfect/socket/datagram/_core.py +460 -0
- pyfect/socket/datagram/_errors.py +102 -0
- pyfect/socket/datagram/_live.py +675 -0
- pyfect/socket/datagram/_model.py +217 -0
- pyfect/socket/server.py +258 -0
- pyfect/socket/ws.py +484 -0
- pyfect/sql/__init__.py +60 -0
- pyfect/sql/_loop.py +134 -0
- pyfect/sql/clickhouse/__init__.py +74 -0
- pyfect/sql/clickhouse/_connection.py +237 -0
- pyfect/sql/clickhouse/_rows.py +87 -0
- pyfect/sql/clickhouse/client.py +322 -0
- pyfect/sql/clickhouse/config.py +241 -0
- pyfect/sql/clickhouse/driver.py +434 -0
- pyfect/sql/clickhouse/error.py +106 -0
- pyfect/sql/clickhouse/migrator.py +47 -0
- pyfect/sql/clickhouse/params.py +168 -0
- pyfect/sql/client/__init__.py +35 -0
- pyfect/sql/client/_client.py +309 -0
- pyfect/sql/client/_sqlite_acquirers.py +130 -0
- pyfect/sql/client/_transaction.py +410 -0
- pyfect/sql/connection.py +87 -0
- pyfect/sql/d1/__init__.py +48 -0
- pyfect/sql/d1/api.py +270 -0
- pyfect/sql/d1/client.py +344 -0
- pyfect/sql/d1/config.py +55 -0
- pyfect/sql/error.py +311 -0
- pyfect/sql/libsql/__init__.py +58 -0
- pyfect/sql/libsql/_connection.py +110 -0
- pyfect/sql/libsql/_hrana.py +258 -0
- pyfect/sql/libsql/_local.py +251 -0
- pyfect/sql/libsql/_targets.py +28 -0
- pyfect/sql/libsql/_values.py +46 -0
- pyfect/sql/libsql/client.py +225 -0
- pyfect/sql/libsql/config.py +170 -0
- pyfect/sql/libsql/migrator.py +43 -0
- pyfect/sql/libsql/sdk.py +123 -0
- pyfect/sql/migrator.py +434 -0
- pyfect/sql/model.py +324 -0
- pyfect/sql/mssql/__init__.py +79 -0
- pyfect/sql/mssql/_connection.py +169 -0
- pyfect/sql/mssql/_driver.py +103 -0
- pyfect/sql/mssql/_names.py +9 -0
- pyfect/sql/mssql/_pool.py +125 -0
- pyfect/sql/mssql/_tds.py +447 -0
- pyfect/sql/mssql/_tds_params.py +155 -0
- pyfect/sql/mssql/_worker.py +155 -0
- pyfect/sql/mssql/client.py +344 -0
- pyfect/sql/mssql/config.py +118 -0
- pyfect/sql/mssql/error.py +111 -0
- pyfect/sql/mssql/migrator.py +43 -0
- pyfect/sql/mssql/parameter.py +44 -0
- pyfect/sql/mssql/procedure.py +119 -0
- pyfect/sql/mssql/types.py +137 -0
- pyfect/sql/mysql/__init__.py +60 -0
- pyfect/sql/mysql/_asyncmy.py +168 -0
- pyfect/sql/mysql/_bind.py +76 -0
- pyfect/sql/mysql/_connection.py +156 -0
- pyfect/sql/mysql/_pool.py +63 -0
- pyfect/sql/mysql/_session.py +262 -0
- pyfect/sql/mysql/client.py +244 -0
- pyfect/sql/mysql/config.py +108 -0
- pyfect/sql/mysql/error.py +104 -0
- pyfect/sql/mysql/migrator.py +44 -0
- pyfect/sql/pg/__init__.py +82 -0
- pyfect/sql/pg/_resolve.py +284 -0
- pyfect/sql/pg/_session.py +380 -0
- pyfect/sql/pg/client.py +398 -0
- pyfect/sql/pg/config.py +142 -0
- pyfect/sql/pg/connection.py +585 -0
- pyfect/sql/pg/error.py +131 -0
- pyfect/sql/pg/migrator.py +139 -0
- pyfect/sql/pg/pool.py +265 -0
- pyfect/sql/resolver.py +294 -0
- pyfect/sql/result.py +24 -0
- pyfect/sql/schema.py +116 -0
- pyfect/sql/sqlite/__init__.py +26 -0
- pyfect/sql/sqlite/_connection.py +230 -0
- pyfect/sql/sqlite/_worker.py +71 -0
- pyfect/sql/sqlite/client.py +248 -0
- pyfect/sql/sqlite/migrator.py +43 -0
- pyfect/sql/statement/__init__.py +144 -0
- pyfect/sql/statement/_compiler.py +384 -0
- pyfect/sql/statement/_segments.py +487 -0
- pyfect/sql/statement/_statement.py +526 -0
- pyfect/sql/statement/_transforms.py +93 -0
- pyfect/sql/stream.py +88 -0
- pyfect/stdio.py +206 -0
- pyfect/stream/__init__.py +151 -0
- pyfect/stream/_async_iterable.py +129 -0
- pyfect/stream/_base.py +69 -0
- pyfect/stream/_bridge_methods.py +139 -0
- pyfect/stream/_catch.py +135 -0
- pyfect/stream/_catch_reason.py +558 -0
- pyfect/stream/_channel_methods.py +61 -0
- pyfect/stream/_chunk_methods.py +169 -0
- pyfect/stream/_chunking.py +310 -0
- pyfect/stream/_concat.py +65 -0
- pyfect/stream/_constructors.py +341 -0
- pyfect/stream/_context.py +147 -0
- pyfect/stream/_error_methods.py +254 -0
- pyfect/stream/_execution_plan.py +199 -0
- pyfect/stream/_fanout.py +154 -0
- pyfect/stream/_fanout_methods.py +183 -0
- pyfect/stream/_filter_methods.py +203 -0
- pyfect/stream/_grouping.py +168 -0
- pyfect/stream/_many.py +65 -0
- pyfect/stream/_map_methods.py +233 -0
- pyfect/stream/_mapping.py +126 -0
- pyfect/stream/_run.py +114 -0
- pyfect/stream/_run_methods.py +114 -0
- pyfect/stream/_service_methods.py +36 -0
- pyfect/stream/_sink_methods.py +151 -0
- pyfect/stream/_sinking.py +277 -0
- pyfect/stream/_sources.py +124 -0
- pyfect/stream/_stream.py +162 -0
- pyfect/stream/_taking.py +330 -0
- pyfect/stream/_text.py +43 -0
- pyfect/stream/_timing.py +294 -0
- pyfect/stream/_timing_methods.py +252 -0
- pyfect/stream/_zip_methods.py +279 -0
- pyfect/stream/_zipping.py +213 -0
- pyfect/subscription_ref.py +66 -0
- pyfect/synchronized_ref.py +38 -0
- pyfect/take.py +36 -0
- pyfect/terminal/__init__.py +41 -0
- pyfect/terminal/_keys.py +113 -0
- pyfect/terminal/_live.py +223 -0
- pyfect/terminal/_service.py +73 -0
- pyfect/testing/__init__.py +122 -0
- pyfect/testing/_assertions.py +92 -0
- pyfect/testing/_clock.py +309 -0
- pyfect/testing/_console.py +164 -0
- pyfect/testing/_crypto.py +44 -0
- pyfect/testing/_decorators.py +211 -0
- pyfect/testing/_flaky.py +34 -0
- pyfect/testing/_layers.py +153 -0
- pyfect/testing/_prop.py +255 -0
- pyfect/testing/_runners.py +84 -0
- pyfect/testing/_runtime.py +174 -0
- pyfect/testing/_schema.py +238 -0
- pyfect/testing/plugin.py +59 -0
- pyfect/tracer/__init__.py +107 -0
- pyfect/tracer/_model.py +176 -0
- pyfect/tracer/_native.py +172 -0
- pyfect/tracer/_refs.py +87 -0
- pyfect/tracer/_spans.py +269 -0
- pyfect/transaction.py +340 -0
- pyfect/trie.py +317 -0
- pyfect/tx_chunk.py +127 -0
- pyfect/tx_deferred.py +75 -0
- pyfect/tx_hash_map.py +265 -0
- pyfect/tx_hash_set.py +151 -0
- pyfect/tx_priority_queue.py +224 -0
- pyfect/tx_pubsub.py +194 -0
- pyfect/tx_queue/__init__.py +27 -0
- pyfect/tx_queue/_queue.py +359 -0
- pyfect/tx_queue/_views.py +60 -0
- pyfect/tx_reentrant_lock.py +201 -0
- pyfect/tx_ref.py +79 -0
- pyfect/tx_semaphore.py +136 -0
- pyfect/tx_subscription_ref.py +118 -0
- pyfect/workers/__init__.py +62 -0
- pyfect/workers/_frames.py +82 -0
- pyfect/workers/_mailbox.py +51 -0
- pyfect/workers/_port.py +106 -0
- pyfect/workers/_process.py +155 -0
- pyfect/workers/_thread.py +100 -0
- pyfect/workers/python_worker.py +133 -0
- pyfect/workers/python_worker_runner.py +112 -0
- pyfect/workers/transferable.py +103 -0
- pyfect/workers/worker.py +273 -0
- pyfect/workers/worker_error.py +86 -0
- pyfect/workers/worker_runner.py +85 -0
- pyfect/workflow/__init__.py +78 -0
- pyfect/workflow/_crypto.py +23 -0
- pyfect/workflow/_encoded.py +381 -0
- pyfect/workflow/_memory.py +424 -0
- pyfect/workflow/_result.py +43 -0
- pyfect/workflow/_services.py +241 -0
- pyfect/workflow/_suspension.py +153 -0
- pyfect/workflow/activity.py +427 -0
- pyfect/workflow/durable_clock.py +72 -0
- pyfect/workflow/durable_deferred.py +403 -0
- pyfect/workflow/durable_queue.py +259 -0
- pyfect/workflow/workflow.py +509 -0
- pyfect/workflow/workflow_engine.py +42 -0
- pyfect/workflow/workflow_proxy.py +161 -0
- pyfect/workflow/workflow_proxy_server.py +146 -0
- pyfect_dev-0.1.0.dist-info/METADATA +296 -0
- pyfect_dev-0.1.0.dist-info/RECORD +1265 -0
- pyfect_dev-0.1.0.dist-info/WHEEL +4 -0
- pyfect_dev-0.1.0.dist-info/entry_points.txt +3 -0
- pyfect_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1048 @@
|
|
|
1
|
+
"""One client's side of an MCP server: its session and the handling of
|
|
2
|
+
each JSON-RPC message it sends.
|
|
3
|
+
|
|
4
|
+
A ``Connection`` is a stdio process's client or an HTTP session.
|
|
5
|
+
``dispatch`` takes one parsed message and writes what it causes through
|
|
6
|
+
``out``: a request is served on its own fiber (so the client can answer
|
|
7
|
+
the server's requests meanwhile, or cancel it) and its response written
|
|
8
|
+
when done; a response to a server request completes the waiting call.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import itertools
|
|
14
|
+
from collections.abc import Callable, Mapping
|
|
15
|
+
from dataclasses import dataclass, field
|
|
16
|
+
from typing import Any, Literal, Never, cast
|
|
17
|
+
|
|
18
|
+
from pyfect import _json_text
|
|
19
|
+
from pyfect import schema as S
|
|
20
|
+
from pyfect._core import Effect
|
|
21
|
+
from pyfect._fiber import Fiber
|
|
22
|
+
from pyfect.ai._mcp import log_levels, param_headers, projection, reverse_policy, subscriptions
|
|
23
|
+
from pyfect.ai._mcp.core import McpServerService, RequestOutbound, RequestServices
|
|
24
|
+
from pyfect.ai._mcp.http_status import fail_with_http_status
|
|
25
|
+
from pyfect.ai._mcp.input_wire import CONTINUATION_PARAMS, INPUT_REQUESTS, ContinuationParams
|
|
26
|
+
from pyfect.ai.mcp_protocol import AnyProtocolAdapter, Registry
|
|
27
|
+
from pyfect.ai.mcp_schema import (
|
|
28
|
+
CALL_TOOL_PARAMS,
|
|
29
|
+
COMPLETE_PARAMS,
|
|
30
|
+
CREATE_MESSAGE_RESULT,
|
|
31
|
+
ELICIT_RESULT,
|
|
32
|
+
GET_PROMPT,
|
|
33
|
+
HEADER_MISMATCH_ERROR_CODE,
|
|
34
|
+
IMPLEMENTATION,
|
|
35
|
+
INITIALIZE_PARAMS,
|
|
36
|
+
LIST_ROOTS_RESULT,
|
|
37
|
+
LIST_TOOLS,
|
|
38
|
+
METHOD_NOT_FOUND_ERROR_CODE,
|
|
39
|
+
MISSING_REQUIRED_CLIENT_CAPABILITY_ERROR_CODE,
|
|
40
|
+
READ_RESOURCE,
|
|
41
|
+
SET_LEVEL,
|
|
42
|
+
SUBSCRIBE,
|
|
43
|
+
SUBSCRIPTIONS_LISTEN,
|
|
44
|
+
UNSUPPORTED_PROTOCOL_VERSION_ERROR_CODE,
|
|
45
|
+
CallToolParams,
|
|
46
|
+
CallToolResult,
|
|
47
|
+
ClientCapabilities,
|
|
48
|
+
CreateMessageParams,
|
|
49
|
+
CreateMessageResult,
|
|
50
|
+
ElicitRequestParams,
|
|
51
|
+
ElicitResult,
|
|
52
|
+
GetPromptResult,
|
|
53
|
+
Icon,
|
|
54
|
+
Implementation,
|
|
55
|
+
InputRequired,
|
|
56
|
+
InternalError,
|
|
57
|
+
InvalidParams,
|
|
58
|
+
InvalidRequest,
|
|
59
|
+
JsonObject,
|
|
60
|
+
ListRootsResult,
|
|
61
|
+
LoggingLevel,
|
|
62
|
+
McpError,
|
|
63
|
+
McpInputResponse,
|
|
64
|
+
McpRequestContext,
|
|
65
|
+
McpReverseOperationError,
|
|
66
|
+
McpReverseOperationUnsupported,
|
|
67
|
+
McpServerClient,
|
|
68
|
+
ProtocolError,
|
|
69
|
+
RequestContext,
|
|
70
|
+
RequestId,
|
|
71
|
+
ReverseClient,
|
|
72
|
+
ReverseError,
|
|
73
|
+
ReverseOperation,
|
|
74
|
+
ServerCapabilities,
|
|
75
|
+
ServerClient,
|
|
76
|
+
parse_request_id,
|
|
77
|
+
)
|
|
78
|
+
from pyfect.cause import Cause, Fail
|
|
79
|
+
from pyfect.deferred import Deferred
|
|
80
|
+
from pyfect.effect import (
|
|
81
|
+
fail,
|
|
82
|
+
fail_cause,
|
|
83
|
+
gen,
|
|
84
|
+
interrupt,
|
|
85
|
+
provide_service,
|
|
86
|
+
service_option,
|
|
87
|
+
succeed,
|
|
88
|
+
suspend,
|
|
89
|
+
sync,
|
|
90
|
+
)
|
|
91
|
+
from pyfect.exit import Success
|
|
92
|
+
from pyfect.http.server_request import HttpServerRequest, ServerRequest
|
|
93
|
+
from pyfect.logger import CurrentLogLevel, Severity
|
|
94
|
+
from pyfect.option import Option
|
|
95
|
+
from pyfect.scope import scoped
|
|
96
|
+
|
|
97
|
+
type Wire = dict[str, Any]
|
|
98
|
+
"""A result as written."""
|
|
99
|
+
|
|
100
|
+
type Send = Callable[[JsonObject], Effect[None]]
|
|
101
|
+
"""Writes a message to the client."""
|
|
102
|
+
|
|
103
|
+
PROTOCOL_VERSION_META = "io.modelcontextprotocol/protocolVersion"
|
|
104
|
+
CLIENT_INFO_META = "io.modelcontextprotocol/clientInfo"
|
|
105
|
+
CLIENT_CAPABILITIES_META = "io.modelcontextprotocol/clientCapabilities"
|
|
106
|
+
SERVER_INFO_META = "io.modelcontextprotocol/serverInfo"
|
|
107
|
+
|
|
108
|
+
_FEATURE_HANDLER_FAILED = "MCP feature handler failed"
|
|
109
|
+
|
|
110
|
+
_VOID: Effect[None] = succeed(None)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
114
|
+
class ServerInfo:
|
|
115
|
+
"""How the server describes itself."""
|
|
116
|
+
|
|
117
|
+
name: str
|
|
118
|
+
version: str
|
|
119
|
+
title: str | None = None
|
|
120
|
+
description: str | None = None
|
|
121
|
+
website_url: str | None = None
|
|
122
|
+
icons: tuple[Icon, ...] = ()
|
|
123
|
+
instructions: str | None = None
|
|
124
|
+
extensions: Mapping[str, Any] | None = None
|
|
125
|
+
|
|
126
|
+
def implementation(self) -> Implementation:
|
|
127
|
+
info: Implementation = {"name": self.name, "version": self.version}
|
|
128
|
+
if self.title is not None:
|
|
129
|
+
info["title"] = self.title
|
|
130
|
+
if self.description is not None:
|
|
131
|
+
info["description"] = self.description
|
|
132
|
+
if self.website_url is not None:
|
|
133
|
+
info["websiteUrl"] = self.website_url
|
|
134
|
+
if self.icons:
|
|
135
|
+
info["icons"] = list(self.icons)
|
|
136
|
+
return info
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def response(id: RequestId | None, result: Mapping[str, Any]) -> JsonObject:
|
|
140
|
+
return {"jsonrpc": "2.0", "id": id, "result": dict(result)}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def error_response(id: RequestId | None, error: McpError) -> JsonObject:
|
|
144
|
+
return {"jsonrpc": "2.0", "id": id, "error": cast("Any", error.to_wire())}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _encodable_response(id: RequestId, result: Wire) -> JsonObject:
|
|
148
|
+
"""The response carrying ``result``, or an internal error when it is not
|
|
149
|
+
JSON (a NaN, a value with no JSON form)."""
|
|
150
|
+
reply = response(id, result)
|
|
151
|
+
try:
|
|
152
|
+
_json_text.stringify(reply)
|
|
153
|
+
except (TypeError, ValueError):
|
|
154
|
+
return error_response(id, InternalError("Failed to encode the result"))
|
|
155
|
+
return reply
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def invalid_params_response(id: RequestId | None) -> JsonObject:
|
|
159
|
+
"""The answer to a request whose params are not an object."""
|
|
160
|
+
return error_response(id, InvalidParams("Invalid params"))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def notification(method: str, params: JsonObject) -> JsonObject:
|
|
164
|
+
return {"jsonrpc": "2.0", "method": method, "params": params}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
type Kind = Literal["request", "notification", "response", "invalid", "invalid_params", "ignored"]
|
|
168
|
+
"""``invalid_params``: a request whose params are not an object; ``ignored``:
|
|
169
|
+
a notification whose params are not (answered by nothing)."""
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass(frozen=True, slots=True)
|
|
173
|
+
class Request:
|
|
174
|
+
"""A request to serve: its id was parsed, so it is one."""
|
|
175
|
+
|
|
176
|
+
id: RequestId
|
|
177
|
+
method: str
|
|
178
|
+
params: JsonObject
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dataclass(frozen=True, slots=True)
|
|
182
|
+
class Classified:
|
|
183
|
+
"""What a parsed JSON value is, with the id to answer it with (and the
|
|
184
|
+
request, when it is one)."""
|
|
185
|
+
|
|
186
|
+
kind: Kind
|
|
187
|
+
id: RequestId | None = None
|
|
188
|
+
method: str = ""
|
|
189
|
+
params: JsonObject = field(default_factory=dict[str, Any])
|
|
190
|
+
body: JsonObject = field(default_factory=dict[str, Any])
|
|
191
|
+
request: Request | None = None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def classify(message: object) -> Classified: # noqa: PLR0911
|
|
195
|
+
if not isinstance(message, dict):
|
|
196
|
+
return Classified("invalid")
|
|
197
|
+
body = cast("JsonObject", message)
|
|
198
|
+
id = parse_request_id(body.get("id"))
|
|
199
|
+
if body.get("jsonrpc") != "2.0":
|
|
200
|
+
return Classified("invalid", id)
|
|
201
|
+
method = body.get("method")
|
|
202
|
+
params = body.get("params", {})
|
|
203
|
+
if isinstance(method, str):
|
|
204
|
+
if params is not None and not isinstance(params, dict):
|
|
205
|
+
if "id" not in body:
|
|
206
|
+
return Classified("ignored", None, method)
|
|
207
|
+
return (
|
|
208
|
+
Classified("invalid_params", id, method)
|
|
209
|
+
if id is not None
|
|
210
|
+
else Classified("invalid", None, method)
|
|
211
|
+
)
|
|
212
|
+
params_object: JsonObject = params or {}
|
|
213
|
+
if "id" not in body:
|
|
214
|
+
return Classified("notification", None, method, params_object)
|
|
215
|
+
if id is None:
|
|
216
|
+
return Classified("invalid", None, method)
|
|
217
|
+
return Classified(
|
|
218
|
+
"request", id, method, params_object, request=Request(id, method, params_object)
|
|
219
|
+
)
|
|
220
|
+
if "method" not in body and ("result" in body or "error" in body):
|
|
221
|
+
return Classified("response", id, body=body)
|
|
222
|
+
return Classified("invalid", id)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def requested_protocol_version(params: Mapping[str, Any]) -> str | None:
|
|
226
|
+
"""The version a stateless request names in its ``_meta``."""
|
|
227
|
+
meta = params.get("_meta")
|
|
228
|
+
if not isinstance(meta, dict):
|
|
229
|
+
return None
|
|
230
|
+
version = cast("JsonObject", meta).get(PROTOCOL_VERSION_META)
|
|
231
|
+
return version if isinstance(version, str) else None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _decode(schema: S.Top, params: object) -> Effect[Any, InvalidParams]:
|
|
235
|
+
decode = cast("Callable[[object], Effect[Any, S.SchemaError]]", S.decode_unknown_effect(schema))
|
|
236
|
+
return decode(params).map_error(lambda error: InvalidParams(error.message))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _method_not_found_bare() -> McpError:
|
|
240
|
+
return ProtocolError("Method not found", error_code=METHOD_NOT_FOUND_ERROR_CODE)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _method_not_found(method: str) -> McpError:
|
|
244
|
+
return ProtocolError(f"Method not found: {method}", error_code=METHOD_NOT_FOUND_ERROR_CODE)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _as_mcp_error(cause: Cause[object]) -> Effect[Wire, McpError]:
|
|
248
|
+
"""A request's failure as the error to answer with: its own MCP error,
|
|
249
|
+
else an internal error. Interruption stays interruption."""
|
|
250
|
+
if cause.has_interrupts_only:
|
|
251
|
+
return fail_cause(cast("Cause[Never]", cause))
|
|
252
|
+
found = cause.find_recoverable_error()
|
|
253
|
+
if isinstance(found, Fail) and isinstance(found.error, McpError):
|
|
254
|
+
return fail(found.error)
|
|
255
|
+
return fail(InternalError("Internal error"))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@dataclass(frozen=True, slots=True)
|
|
259
|
+
class Identity:
|
|
260
|
+
"""Who sent a request: a session's client, fixed by ``initialize``, or
|
|
261
|
+
a stateless request's own ``_meta``. Passed to each request rather
|
|
262
|
+
than read from the connection, so one request cannot change another's."""
|
|
263
|
+
|
|
264
|
+
capabilities: ClientCapabilities = field(default_factory=ClientCapabilities)
|
|
265
|
+
info: Implementation | None = None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@dataclass(eq=False)
|
|
269
|
+
class Connection:
|
|
270
|
+
"""One client: a stdio process's or an HTTP session's."""
|
|
271
|
+
|
|
272
|
+
server: McpServerService
|
|
273
|
+
info: ServerInfo
|
|
274
|
+
registry: Registry
|
|
275
|
+
client_id: int
|
|
276
|
+
transport: Literal["stdio", "http"]
|
|
277
|
+
broadcast: Send
|
|
278
|
+
default_log_level: Severity
|
|
279
|
+
"""The session's threshold until it sets one: the ``CurrentLogLevel``
|
|
280
|
+
the server was built with (Effect's ``defaultLogLevel``)."""
|
|
281
|
+
protocol: AnyProtocolAdapter | None = None
|
|
282
|
+
identity: Identity = field(default_factory=Identity)
|
|
283
|
+
initialize_payload: JsonObject = field(default_factory=dict[str, Any])
|
|
284
|
+
initialized: bool = False
|
|
285
|
+
closed: bool = False
|
|
286
|
+
"""Whether the session ended (closed by the client, idle or evicted)."""
|
|
287
|
+
streams: list[Send] = field(default_factory=list[Send])
|
|
288
|
+
"""An HTTP session's event streams of the requests in flight, where its
|
|
289
|
+
``broadcast`` writes (a stdio client has the one output)."""
|
|
290
|
+
log_level: LoggingLevel | None = None
|
|
291
|
+
"""The level the client set with ``logging/setLevel``."""
|
|
292
|
+
subscriptions: set[str] = field(default_factory=set[str])
|
|
293
|
+
supports_subscriptions: bool = True
|
|
294
|
+
"""Whether 2026-07-28 clients may ``subscriptions/listen``."""
|
|
295
|
+
_listening: set[RequestId] = field(default_factory=set[RequestId])
|
|
296
|
+
"""The requests that are subscriptions: they end only when cancelled."""
|
|
297
|
+
_pending: dict[RequestId, Deferred[JsonObject]] = field(
|
|
298
|
+
default_factory=dict[RequestId, "Deferred[JsonObject]"]
|
|
299
|
+
)
|
|
300
|
+
_inflight: dict[RequestId, Fiber[Any, Any]] = field(
|
|
301
|
+
default_factory=dict[RequestId, "Fiber[Any, Any]"]
|
|
302
|
+
)
|
|
303
|
+
_reverse_ids: itertools.count[int] = field(default_factory=lambda: itertools.count(1))
|
|
304
|
+
|
|
305
|
+
# --- messages ----------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
def dispatch(self, message: object, out: Send) -> Effect[None]:
|
|
308
|
+
"""Handle one message; a request is answered through ``out`` from
|
|
309
|
+
its own fiber."""
|
|
310
|
+
classified = classify(message)
|
|
311
|
+
request = classified.request
|
|
312
|
+
if request is not None:
|
|
313
|
+
if request.method == "initialize":
|
|
314
|
+
# Answered before the next message, which may rely on it.
|
|
315
|
+
return self.answer(request.id, request.method, request.params, out).flat_map(
|
|
316
|
+
lambda reply: _VOID if reply is None else out(reply)
|
|
317
|
+
)
|
|
318
|
+
return self.start(request, out).as_void()
|
|
319
|
+
return self._not_a_request(classified, out)
|
|
320
|
+
|
|
321
|
+
def _not_a_request(self, classified: Classified, out: Send) -> Effect[None]:
|
|
322
|
+
match classified.kind:
|
|
323
|
+
case "notification":
|
|
324
|
+
return self._notified(classified.method, classified.params)
|
|
325
|
+
case "response":
|
|
326
|
+
return self._answered(classified.body)
|
|
327
|
+
case "ignored":
|
|
328
|
+
return _VOID
|
|
329
|
+
case "invalid_params":
|
|
330
|
+
return out(invalid_params_response(classified.id))
|
|
331
|
+
case _:
|
|
332
|
+
return out(error_response(classified.id, InvalidRequest("Invalid Request")))
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def busy(self) -> bool:
|
|
336
|
+
"""Whether a request is still being served."""
|
|
337
|
+
return bool(self._inflight)
|
|
338
|
+
|
|
339
|
+
def close(self) -> Effect[None]:
|
|
340
|
+
"""End the connection: interrupt the requests still being served."""
|
|
341
|
+
|
|
342
|
+
@gen
|
|
343
|
+
def run():
|
|
344
|
+
self.closed = True
|
|
345
|
+
for fiber in list(self._inflight.values()):
|
|
346
|
+
yield from fiber.interrupt()
|
|
347
|
+
|
|
348
|
+
return run
|
|
349
|
+
|
|
350
|
+
def drain(self) -> Effect[None]:
|
|
351
|
+
"""Wait for the requests still being served, ending the subscriptions
|
|
352
|
+
among them: nobody is left to cancel those."""
|
|
353
|
+
|
|
354
|
+
@gen
|
|
355
|
+
def run():
|
|
356
|
+
# Taken at once: awaiting one, another may end by itself (overflow).
|
|
357
|
+
for fiber in [self._inflight[id] for id in self._listening]:
|
|
358
|
+
yield from fiber.interrupt()
|
|
359
|
+
while self._inflight:
|
|
360
|
+
yield from next(iter(self._inflight.values())).await_exit()
|
|
361
|
+
|
|
362
|
+
return run
|
|
363
|
+
|
|
364
|
+
def start(self, request: Request, out: Send) -> Effect[Fiber[None, Never]]:
|
|
365
|
+
"""Serve ``request`` on its own fiber, which ends once its response
|
|
366
|
+
is written or it is cancelled (writing nothing)."""
|
|
367
|
+
id = request.id
|
|
368
|
+
|
|
369
|
+
def done() -> None:
|
|
370
|
+
self._inflight.pop(id, None)
|
|
371
|
+
self._listening.discard(id)
|
|
372
|
+
|
|
373
|
+
served = (
|
|
374
|
+
self.answer(id, request.method, request.params, out)
|
|
375
|
+
.flat_map(lambda reply: _VOID if reply is None else out(reply))
|
|
376
|
+
.ensuring(sync(done))
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
def track(fiber: Fiber[None, Never]) -> Fiber[None, Never]:
|
|
380
|
+
self._inflight[id] = fiber
|
|
381
|
+
if request.method == SUBSCRIPTIONS_LISTEN.name:
|
|
382
|
+
self._listening.add(id)
|
|
383
|
+
return fiber
|
|
384
|
+
|
|
385
|
+
return served.fork_daemon().map(track)
|
|
386
|
+
|
|
387
|
+
def answer(
|
|
388
|
+
self, id: RequestId, method: str, params: JsonObject, out: Send
|
|
389
|
+
) -> Effect[JsonObject | None]:
|
|
390
|
+
"""The response to a request (``None`` when it was cancelled)."""
|
|
391
|
+
version = requested_protocol_version(params)
|
|
392
|
+
stateless = self.registry.find(version) if version is not None else None
|
|
393
|
+
if stateless is not None and stateless.is_stateless:
|
|
394
|
+
answered = self._stateless(stateless, id, method, params, out)
|
|
395
|
+
elif version is not None and stateless is None and _offers_stateless(self.registry):
|
|
396
|
+
answered = fail(_unsupported_version(version, self.registry))
|
|
397
|
+
else:
|
|
398
|
+
answered = self._stateful(method, params, out)
|
|
399
|
+
# Effect's McpServer is an RpcServer with spanPrefix "McpServer".
|
|
400
|
+
traced = answered.with_span(
|
|
401
|
+
f"McpServer.{method}",
|
|
402
|
+
kind="server",
|
|
403
|
+
capture_stack_trace=False,
|
|
404
|
+
attributes={"rpc.system.name": "effect_rpc", "rpc.method": method},
|
|
405
|
+
)
|
|
406
|
+
# Effect's RpcServer reports the cause of every failed request.
|
|
407
|
+
replied: Effect[JsonObject] = (
|
|
408
|
+
traced.with_error_reporting()
|
|
409
|
+
.catch_cause(_as_mcp_error)
|
|
410
|
+
.match(
|
|
411
|
+
on_failure=lambda error: error_response(id, error),
|
|
412
|
+
on_success=lambda result: _encodable_response(id, result),
|
|
413
|
+
)
|
|
414
|
+
)
|
|
415
|
+
return replied.exit().map(lambda exit: exit.value if isinstance(exit, Success) else None)
|
|
416
|
+
|
|
417
|
+
def _notified(self, method: str, params: JsonObject) -> Effect[None]:
|
|
418
|
+
if method == "notifications/initialized" and self.protocol is not None:
|
|
419
|
+
return sync(lambda: setattr(self, "initialized", True))
|
|
420
|
+
if method == "notifications/cancelled":
|
|
421
|
+
request_id = parse_request_id(params.get("requestId"))
|
|
422
|
+
fiber = None if request_id is None else self._inflight.get(request_id)
|
|
423
|
+
if fiber is not None:
|
|
424
|
+
return sync(lambda: fiber.interrupt_as_unsafe(None))
|
|
425
|
+
return _VOID
|
|
426
|
+
|
|
427
|
+
def _answered(self, body: JsonObject) -> Effect[None]:
|
|
428
|
+
id = parse_request_id(body.get("id"))
|
|
429
|
+
deferred = None if id is None else self._pending.get(id)
|
|
430
|
+
return _VOID if deferred is None else deferred.succeed(body).as_void()
|
|
431
|
+
|
|
432
|
+
# --- notifications -----------------------------------------------------------------
|
|
433
|
+
|
|
434
|
+
def wants(self, method: str, params: Mapping[str, Any]) -> bool:
|
|
435
|
+
if method == "notifications/message":
|
|
436
|
+
threshold = self.default_log_level if self.log_level is None else self.log_level
|
|
437
|
+
return log_levels.session_delivers(params.get("level"), threshold)
|
|
438
|
+
if method == "notifications/resources/updated":
|
|
439
|
+
return params.get("uri") in self.subscriptions
|
|
440
|
+
if method == "notifications/elicitation/complete":
|
|
441
|
+
return self.protocol is not None and self.protocol.supports_icons
|
|
442
|
+
return True
|
|
443
|
+
|
|
444
|
+
def deliver(self, method: str, params: JsonObject) -> Effect[None]:
|
|
445
|
+
"""A broadcast notification, sent once the client is initialized
|
|
446
|
+
and if it wants it (``Peer``)."""
|
|
447
|
+
protocol = self.protocol
|
|
448
|
+
if protocol is None or not self.initialized or not self.wants(method, params):
|
|
449
|
+
return _VOID
|
|
450
|
+
return self.broadcast(
|
|
451
|
+
notification(method, projection.notification(protocol, method, params))
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
def _request_outbound(
|
|
455
|
+
self, out: Send, protocol: AnyProtocolAdapter, metadata: JsonObject
|
|
456
|
+
) -> Callable[[str, JsonObject], Effect[None]]:
|
|
457
|
+
"""Where a request's own notifications go. A 2026-07-28 request gets
|
|
458
|
+
the logs its ``_meta`` asks for and the progress of its own token."""
|
|
459
|
+
|
|
460
|
+
def stateless_wants(method: str, params: JsonObject) -> bool:
|
|
461
|
+
if method == "notifications/message":
|
|
462
|
+
minimum = log_levels.request_log_level(metadata)
|
|
463
|
+
level = params.get("level")
|
|
464
|
+
return (
|
|
465
|
+
log_levels.is_logging_level(minimum)
|
|
466
|
+
and log_levels.is_logging_level(level)
|
|
467
|
+
and log_levels.at_least(level, minimum)
|
|
468
|
+
)
|
|
469
|
+
if method == "notifications/progress":
|
|
470
|
+
return params.get("progressToken") == metadata.get("progressToken")
|
|
471
|
+
return self.wants(method, params)
|
|
472
|
+
|
|
473
|
+
wants = stateless_wants if protocol.is_stateless else self.wants
|
|
474
|
+
|
|
475
|
+
def deliver(method: str, params: JsonObject) -> Effect[None]:
|
|
476
|
+
if not wants(method, params):
|
|
477
|
+
return _VOID
|
|
478
|
+
return out(notification(method, projection.notification(protocol, method, params)))
|
|
479
|
+
|
|
480
|
+
return deliver
|
|
481
|
+
|
|
482
|
+
def _handler_log_level(self, protocol: AnyProtocolAdapter, metadata: JsonObject) -> Severity:
|
|
483
|
+
"""The ``CurrentLogLevel`` a handler runs at: its request's level on
|
|
484
|
+
2026-07-28, else the session's."""
|
|
485
|
+
if protocol.is_stateless:
|
|
486
|
+
requested = log_levels.request_log_level(metadata)
|
|
487
|
+
if log_levels.is_logging_level(requested):
|
|
488
|
+
return log_levels.effect_level(requested)
|
|
489
|
+
return self.default_log_level
|
|
490
|
+
if self.log_level is None:
|
|
491
|
+
return self.default_log_level
|
|
492
|
+
return log_levels.effect_level(self.log_level)
|
|
493
|
+
|
|
494
|
+
# --- requests to the client --------------------------------------------------------
|
|
495
|
+
|
|
496
|
+
def _reverse_request(
|
|
497
|
+
self, operation: ReverseOperation, params: Mapping[str, Any], out: Send
|
|
498
|
+
) -> Effect[JsonObject, ReverseError]:
|
|
499
|
+
id = f"server-{self.client_id}-{next(self._reverse_ids)}"
|
|
500
|
+
deferred = Deferred[JsonObject]()
|
|
501
|
+
message: JsonObject = {
|
|
502
|
+
"jsonrpc": "2.0",
|
|
503
|
+
"id": id,
|
|
504
|
+
"method": operation,
|
|
505
|
+
"params": dict(params),
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
def answer(reply: JsonObject) -> Effect[JsonObject, ReverseError]:
|
|
509
|
+
error = reply.get("error")
|
|
510
|
+
if isinstance(error, dict):
|
|
511
|
+
return fail(McpReverseOperationError(operation, error))
|
|
512
|
+
result = reply.get("result")
|
|
513
|
+
return succeed(result if isinstance(result, dict) else {})
|
|
514
|
+
|
|
515
|
+
return (
|
|
516
|
+
sync(lambda: self._pending.__setitem__(id, deferred))
|
|
517
|
+
.and_then(out(message))
|
|
518
|
+
.and_then(
|
|
519
|
+
# A cancelled request may never be answered: tell the client.
|
|
520
|
+
deferred.wait().on_interrupt(
|
|
521
|
+
out(notification("notifications/cancelled", {"requestId": id}))
|
|
522
|
+
)
|
|
523
|
+
)
|
|
524
|
+
.flat_map(answer)
|
|
525
|
+
.ensuring(sync(lambda: self._pending.pop(id, None)))
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
def _reverse_client(
|
|
529
|
+
self, protocol: AnyProtocolAdapter, identity: Identity, out: Send
|
|
530
|
+
) -> ReverseClient:
|
|
531
|
+
version = protocol.protocol_version
|
|
532
|
+
|
|
533
|
+
def refused(
|
|
534
|
+
operation: ReverseOperation, capability: Literal["roots", "sampling", "elicitation"]
|
|
535
|
+
) -> McpReverseOperationUnsupported | None:
|
|
536
|
+
def unsupported(reason: str) -> McpReverseOperationUnsupported:
|
|
537
|
+
return McpReverseOperationUnsupported(operation, version, reason)
|
|
538
|
+
|
|
539
|
+
if capability == "elicitation" and not protocol.supports_structured_content:
|
|
540
|
+
return unsupported(f"MCP {version} has no elicitation")
|
|
541
|
+
if identity.capabilities.get(capability) is None:
|
|
542
|
+
return unsupported(f"Client did not advertise the {capability} capability")
|
|
543
|
+
return None
|
|
544
|
+
|
|
545
|
+
def request[A](
|
|
546
|
+
operation: ReverseOperation,
|
|
547
|
+
capability: Literal["roots", "sampling", "elicitation"],
|
|
548
|
+
params: Mapping[str, Any],
|
|
549
|
+
result: S.Codec[A, Any],
|
|
550
|
+
) -> Effect[A, ReverseError]:
|
|
551
|
+
"""The client's answer, decoded with ``result`` (a malformed one
|
|
552
|
+
is an ``McpReverseOperationError``, never taken as is)."""
|
|
553
|
+
error = refused(operation, capability)
|
|
554
|
+
if error is not None:
|
|
555
|
+
return fail(error)
|
|
556
|
+
decode = cast(
|
|
557
|
+
"Callable[[object], Effect[A, S.SchemaError]]", S.decode_unknown_effect(result)
|
|
558
|
+
)
|
|
559
|
+
return self._reverse_request(operation, params, out).flat_map(
|
|
560
|
+
lambda reply: decode(reply).map_error(
|
|
561
|
+
lambda invalid: McpReverseOperationError(operation, invalid)
|
|
562
|
+
)
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
def list_roots() -> Effect[ListRootsResult, ReverseError]:
|
|
566
|
+
return request("roots/list", "roots", {}, LIST_ROOTS_RESULT)
|
|
567
|
+
|
|
568
|
+
def create_message(
|
|
569
|
+
params: CreateMessageParams,
|
|
570
|
+
) -> Effect[CreateMessageResult, ReverseError]:
|
|
571
|
+
operation: ReverseOperation = "sampling/createMessage"
|
|
572
|
+
refusal = refused(operation, "sampling")
|
|
573
|
+
reason = reverse_policy.sampling_refusal(protocol, identity.capabilities, params)
|
|
574
|
+
if refusal is None and reason is not None:
|
|
575
|
+
return fail(McpReverseOperationUnsupported(operation, version, reason))
|
|
576
|
+
|
|
577
|
+
def checked(result: CreateMessageResult) -> Effect[CreateMessageResult, ReverseError]:
|
|
578
|
+
reason = reverse_policy.sampling_result_refusal(
|
|
579
|
+
protocol, identity.capabilities, result
|
|
580
|
+
)
|
|
581
|
+
if reason is None:
|
|
582
|
+
return succeed(result)
|
|
583
|
+
return fail(McpReverseOperationUnsupported(operation, version, reason))
|
|
584
|
+
|
|
585
|
+
return request(operation, "sampling", params, CREATE_MESSAGE_RESULT).flat_map(checked)
|
|
586
|
+
|
|
587
|
+
def elicit(params: ElicitRequestParams) -> Effect[ElicitResult, ReverseError]:
|
|
588
|
+
if refused("elicitation/create", "elicitation") is None:
|
|
589
|
+
projected = reverse_policy.project_elicitation(
|
|
590
|
+
protocol, identity.capabilities, params
|
|
591
|
+
)
|
|
592
|
+
if isinstance(projected, str):
|
|
593
|
+
return fail(
|
|
594
|
+
McpReverseOperationUnsupported("elicitation/create", version, projected)
|
|
595
|
+
)
|
|
596
|
+
params = projected
|
|
597
|
+
return request("elicitation/create", "elicitation", params, ELICIT_RESULT)
|
|
598
|
+
|
|
599
|
+
return ReverseClient(list_roots, create_message, elicit)
|
|
600
|
+
|
|
601
|
+
# --- serving -----------------------------------------------------------------------
|
|
602
|
+
|
|
603
|
+
def _serve(
|
|
604
|
+
self,
|
|
605
|
+
protocol: AnyProtocolAdapter,
|
|
606
|
+
identity: Identity,
|
|
607
|
+
params: JsonObject,
|
|
608
|
+
out: Send,
|
|
609
|
+
handler: Callable[[RequestContext], Effect[Wire, McpError, RequestServices]],
|
|
610
|
+
*,
|
|
611
|
+
continuation: ContinuationParams | None = None,
|
|
612
|
+
) -> Effect[Wire, McpError]:
|
|
613
|
+
"""``handler`` given the request's services."""
|
|
614
|
+
meta = params.get("_meta")
|
|
615
|
+
metadata: JsonObject = meta if isinstance(meta, dict) else {}
|
|
616
|
+
version = protocol.protocol_version
|
|
617
|
+
responses: Mapping[str, McpInputResponse] | None = None
|
|
618
|
+
state: str | None = None
|
|
619
|
+
if continuation is not None:
|
|
620
|
+
responses = continuation.get("inputResponses")
|
|
621
|
+
state = continuation.get("requestState")
|
|
622
|
+
context = RequestContext(
|
|
623
|
+
self.client_id,
|
|
624
|
+
version,
|
|
625
|
+
identity.capabilities,
|
|
626
|
+
identity.info,
|
|
627
|
+
metadata,
|
|
628
|
+
responses,
|
|
629
|
+
state,
|
|
630
|
+
)
|
|
631
|
+
client = ServerClient(
|
|
632
|
+
self.client_id,
|
|
633
|
+
version,
|
|
634
|
+
identity.capabilities,
|
|
635
|
+
identity.info,
|
|
636
|
+
self.initialize_payload,
|
|
637
|
+
succeed(self._reverse_client(protocol, identity, out)),
|
|
638
|
+
metadata,
|
|
639
|
+
)
|
|
640
|
+
# A stateless request belongs to no session, so it has no client of one.
|
|
641
|
+
with_client: Effect[Wire, McpError, Any] = suspend(lambda: handler(context))
|
|
642
|
+
if not protocol.is_stateless:
|
|
643
|
+
with_client = with_client.pipe(provide_service(McpServerClient, client))
|
|
644
|
+
return (
|
|
645
|
+
with_client.pipe(provide_service(McpRequestContext, context))
|
|
646
|
+
.pipe(provide_service(CurrentLogLevel, self._handler_log_level(protocol, metadata)))
|
|
647
|
+
.pipe(provide_service(RequestOutbound, self._request_outbound(out, protocol, metadata)))
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
def _stateful(self, method: str, params: JsonObject, out: Send) -> Effect[Wire, McpError]:
|
|
651
|
+
if method == "ping":
|
|
652
|
+
return succeed({})
|
|
653
|
+
if method == "initialize":
|
|
654
|
+
return self._initialize(params)
|
|
655
|
+
protocol = self.protocol
|
|
656
|
+
if protocol is None:
|
|
657
|
+
return fail(InvalidRequest("Server not initialized"))
|
|
658
|
+
return self._feature(protocol, self.identity, method, params, out)
|
|
659
|
+
|
|
660
|
+
def _initialize(self, params: JsonObject) -> Effect[Wire, McpError]:
|
|
661
|
+
offered = params.get("protocolVersion")
|
|
662
|
+
protocol = self.registry.select_stateful(offered if isinstance(offered, str) else None)
|
|
663
|
+
if protocol is None:
|
|
664
|
+
return fail(_method_not_found("initialize"))
|
|
665
|
+
|
|
666
|
+
def start(payload: Any) -> Wire:
|
|
667
|
+
self.protocol = protocol
|
|
668
|
+
capabilities = projection.client_capabilities(protocol, payload["capabilities"])
|
|
669
|
+
self.identity = Identity(capabilities, payload["clientInfo"])
|
|
670
|
+
self.initialize_payload = dict(params)
|
|
671
|
+
result: Wire = {
|
|
672
|
+
"protocolVersion": protocol.protocol_version,
|
|
673
|
+
"capabilities": cast("Any", self.capabilities_for(protocol)),
|
|
674
|
+
"serverInfo": projection.implementation(protocol, self.info.implementation()),
|
|
675
|
+
}
|
|
676
|
+
if self.info.instructions is not None:
|
|
677
|
+
result["instructions"] = self.info.instructions
|
|
678
|
+
return result
|
|
679
|
+
|
|
680
|
+
return _decode(INITIALIZE_PARAMS, params).map(start)
|
|
681
|
+
|
|
682
|
+
def capabilities_for(self, protocol: AnyProtocolAdapter) -> ServerCapabilities:
|
|
683
|
+
presence = self.server.has_registrations
|
|
684
|
+
# A session's client always gets list changes; only 2026-07-28 opts out.
|
|
685
|
+
list_changed = self.supports_subscriptions if protocol.is_stateless else True
|
|
686
|
+
subscribe = list_changed if protocol.is_stateless else self.transport == "stdio"
|
|
687
|
+
capabilities: ServerCapabilities = {"logging": {}}
|
|
688
|
+
if protocol.supports_audio:
|
|
689
|
+
capabilities["completions"] = {}
|
|
690
|
+
extensions = _extensions(protocol, self.info.extensions)
|
|
691
|
+
if extensions:
|
|
692
|
+
capabilities["extensions"] = extensions
|
|
693
|
+
if presence.prompts:
|
|
694
|
+
capabilities["prompts"] = {"listChanged": list_changed}
|
|
695
|
+
if presence.resources:
|
|
696
|
+
capabilities["resources"] = {"listChanged": list_changed, "subscribe": subscribe}
|
|
697
|
+
if presence.tools:
|
|
698
|
+
capabilities["tools"] = {"listChanged": list_changed}
|
|
699
|
+
return capabilities
|
|
700
|
+
|
|
701
|
+
def _feature( # noqa: PLR0911
|
|
702
|
+
self,
|
|
703
|
+
protocol: AnyProtocolAdapter,
|
|
704
|
+
identity: Identity,
|
|
705
|
+
method: str,
|
|
706
|
+
params: JsonObject,
|
|
707
|
+
out: Send,
|
|
708
|
+
) -> Effect[Wire, McpError]:
|
|
709
|
+
server = self.server
|
|
710
|
+
|
|
711
|
+
def serve(
|
|
712
|
+
handler: Callable[[RequestContext], Effect[Wire, McpError, RequestServices]],
|
|
713
|
+
) -> Effect[Wire, McpError]:
|
|
714
|
+
return self._serve(protocol, identity, params, out, handler)
|
|
715
|
+
|
|
716
|
+
def serve_continued(
|
|
717
|
+
handler: Callable[[RequestContext], Effect[Wire, McpError, RequestServices]],
|
|
718
|
+
) -> Effect[Wire, McpError]:
|
|
719
|
+
"""``serve`` for a request 2026-07-28 may retry with client input."""
|
|
720
|
+
if not protocol.is_stateless:
|
|
721
|
+
return serve(handler)
|
|
722
|
+
return _decode(CONTINUATION_PARAMS, params).flat_map(
|
|
723
|
+
lambda continuation: self._serve(
|
|
724
|
+
protocol, identity, params, out, handler, continuation=continuation
|
|
725
|
+
)
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
def listed(
|
|
729
|
+
key: str, items: Callable[[RequestContext], list[Any]]
|
|
730
|
+
) -> Effect[Wire, McpError]:
|
|
731
|
+
return serve(lambda c: succeed({key: items(c)}))
|
|
732
|
+
|
|
733
|
+
match method:
|
|
734
|
+
case "logging/setLevel":
|
|
735
|
+
return _decode(SET_LEVEL.payload_schema, params).map(self._set_level)
|
|
736
|
+
case "resources/subscribe" | "resources/unsubscribe" if self.transport == "stdio":
|
|
737
|
+
subscribe = method == "resources/subscribe"
|
|
738
|
+
return _decode(SUBSCRIBE.payload_schema, params).map(
|
|
739
|
+
lambda decoded: self._subscribe(decoded["uri"], subscribe)
|
|
740
|
+
)
|
|
741
|
+
case "tools/list":
|
|
742
|
+
return _decode(LIST_TOOLS.payload_schema, params).and_then(
|
|
743
|
+
listed(
|
|
744
|
+
"tools",
|
|
745
|
+
lambda c: [projection.tool(protocol, t) for t in server.list_tools(c)],
|
|
746
|
+
)
|
|
747
|
+
)
|
|
748
|
+
case "tools/call":
|
|
749
|
+
return _decode(CALL_TOOL_PARAMS, params).flat_map(
|
|
750
|
+
lambda call: serve_continued(lambda c: _call_tool(server, protocol, call, c))
|
|
751
|
+
)
|
|
752
|
+
case "resources/list":
|
|
753
|
+
return listed(
|
|
754
|
+
"resources",
|
|
755
|
+
lambda c: [projection.resource(protocol, r) for r in server.list_resources(c)],
|
|
756
|
+
)
|
|
757
|
+
case "resources/templates/list":
|
|
758
|
+
return listed(
|
|
759
|
+
"resourceTemplates",
|
|
760
|
+
lambda c: [
|
|
761
|
+
projection.resource_template(protocol, t)
|
|
762
|
+
for t in server.list_resource_templates(c)
|
|
763
|
+
],
|
|
764
|
+
)
|
|
765
|
+
case "resources/read":
|
|
766
|
+
return _decode(READ_RESOURCE.payload_schema, params).flat_map(
|
|
767
|
+
lambda decoded: serve_continued(
|
|
768
|
+
lambda c: self._read(protocol, decoded["uri"], c)
|
|
769
|
+
)
|
|
770
|
+
)
|
|
771
|
+
case "prompts/list":
|
|
772
|
+
return listed(
|
|
773
|
+
"prompts",
|
|
774
|
+
lambda c: [projection.prompt(protocol, p) for p in server.list_prompts(c)],
|
|
775
|
+
)
|
|
776
|
+
case "prompts/get":
|
|
777
|
+
return _decode(GET_PROMPT.payload_schema, params).flat_map(
|
|
778
|
+
lambda decoded: serve_continued(
|
|
779
|
+
lambda c: server.get_prompt_for(
|
|
780
|
+
decoded["name"], decoded.get("arguments") or {}, c
|
|
781
|
+
).flat_map(lambda outcome: _prompt_on_wire(protocol, identity, outcome))
|
|
782
|
+
)
|
|
783
|
+
)
|
|
784
|
+
case "completion/complete":
|
|
785
|
+
return _decode(COMPLETE_PARAMS, params).flat_map(
|
|
786
|
+
lambda decoded: serve(lambda _: server.complete_for(decoded).map(_wire))
|
|
787
|
+
)
|
|
788
|
+
case _:
|
|
789
|
+
return fail(_method_not_found(method))
|
|
790
|
+
|
|
791
|
+
def _set_level(self, decoded: Any) -> JsonObject:
|
|
792
|
+
self.log_level = decoded["level"]
|
|
793
|
+
return {}
|
|
794
|
+
|
|
795
|
+
def _subscribe(self, uri: str, subscribe: bool) -> JsonObject:
|
|
796
|
+
if subscribe:
|
|
797
|
+
self.subscriptions.add(uri)
|
|
798
|
+
else:
|
|
799
|
+
self.subscriptions.discard(uri)
|
|
800
|
+
return {}
|
|
801
|
+
|
|
802
|
+
def _read(
|
|
803
|
+
self, protocol: AnyProtocolAdapter, uri: str, context: RequestContext
|
|
804
|
+
) -> Effect[Wire, McpError, RequestServices]:
|
|
805
|
+
found = self.server.read_resource_for(uri, context)
|
|
806
|
+
if found is None:
|
|
807
|
+
return fail(
|
|
808
|
+
ProtocolError(
|
|
809
|
+
f"Resource '{uri}' not found",
|
|
810
|
+
{"uri": uri},
|
|
811
|
+
error_code=protocol.resource_not_found_code,
|
|
812
|
+
)
|
|
813
|
+
)
|
|
814
|
+
return found.map(_wire)
|
|
815
|
+
|
|
816
|
+
# --- 2026-07-28 ----------------------------------------------------------------------
|
|
817
|
+
|
|
818
|
+
def _listen(self, id: RequestId, params: JsonObject, out: Send) -> Effect[Wire, McpError]:
|
|
819
|
+
"""A ``subscriptions/listen`` stream, on ``out``. It has no result: it
|
|
820
|
+
ends when cancelled, or by itself when its backlog overflows."""
|
|
821
|
+
|
|
822
|
+
def send(method: str, body: JsonObject) -> Effect[None]:
|
|
823
|
+
return out(notification(method, body))
|
|
824
|
+
|
|
825
|
+
def terminate(request_id: RequestId, reason: str) -> Effect[None]:
|
|
826
|
+
# Over HTTP the stream's end says it; stdio's has no end of its own.
|
|
827
|
+
if self.transport == "http":
|
|
828
|
+
return _VOID
|
|
829
|
+
return send("notifications/cancelled", {"requestId": request_id, "reason": reason})
|
|
830
|
+
|
|
831
|
+
def listening(decoded: Any) -> Effect[Wire, McpError]:
|
|
832
|
+
if not self.supports_subscriptions:
|
|
833
|
+
return fail(_method_not_found(SUBSCRIPTIONS_LISTEN.name))
|
|
834
|
+
listened = subscriptions.listen(
|
|
835
|
+
decoded["notifications"],
|
|
836
|
+
id,
|
|
837
|
+
presence=self.server.has_registrations,
|
|
838
|
+
list_change_revision=self.server.list_change_revision,
|
|
839
|
+
subscribe=self.server.subscribe_events,
|
|
840
|
+
send=send,
|
|
841
|
+
terminate=terminate,
|
|
842
|
+
)
|
|
843
|
+
return scoped(listened).and_then(interrupt())
|
|
844
|
+
|
|
845
|
+
return _decode(SUBSCRIPTIONS_LISTEN.payload_schema, params).flat_map(listening)
|
|
846
|
+
|
|
847
|
+
def _stateless( # noqa: PLR0911
|
|
848
|
+
self,
|
|
849
|
+
protocol: AnyProtocolAdapter,
|
|
850
|
+
id: RequestId,
|
|
851
|
+
method: str,
|
|
852
|
+
params: JsonObject,
|
|
853
|
+
out: Send,
|
|
854
|
+
) -> Effect[Wire, McpError]:
|
|
855
|
+
meta = cast("JsonObject", params.get("_meta") or {})
|
|
856
|
+
capabilities = meta.get(CLIENT_CAPABILITIES_META)
|
|
857
|
+
info = meta.get(CLIENT_INFO_META)
|
|
858
|
+
if info is not None and not _is_implementation(info):
|
|
859
|
+
return fail(InvalidParams(f"Invalid {CLIENT_INFO_META}"))
|
|
860
|
+
identity = Identity(
|
|
861
|
+
cast("ClientCapabilities", capabilities if isinstance(capabilities, dict) else {}),
|
|
862
|
+
cast("Implementation", info) if isinstance(info, dict) else None,
|
|
863
|
+
)
|
|
864
|
+
server_info = projection.implementation(protocol, self.info.implementation())
|
|
865
|
+
|
|
866
|
+
def complete(result: JsonObject, cacheable: bool) -> JsonObject:
|
|
867
|
+
own = result.get("_meta")
|
|
868
|
+
merged: JsonObject = {**(own if isinstance(own, dict) else {})}
|
|
869
|
+
merged[SERVER_INFO_META] = cast("Any", server_info)
|
|
870
|
+
result_type = "input_required" if isinstance(result, _InputRequired) else "complete"
|
|
871
|
+
completed: JsonObject = {**result, "_meta": merged, "resultType": result_type}
|
|
872
|
+
if cacheable:
|
|
873
|
+
completed["ttlMs"] = 0
|
|
874
|
+
completed["cacheScope"] = "private"
|
|
875
|
+
return completed
|
|
876
|
+
|
|
877
|
+
if method == "server/discover":
|
|
878
|
+
discovery: JsonObject = {
|
|
879
|
+
"supportedVersions": list(self.registry.versions),
|
|
880
|
+
"capabilities": cast("Any", self.capabilities_for(protocol)),
|
|
881
|
+
}
|
|
882
|
+
if self.info.instructions is not None:
|
|
883
|
+
discovery["instructions"] = self.info.instructions
|
|
884
|
+
return succeed(complete(discovery, True))
|
|
885
|
+
if method in _NOT_ON_2026_07_28:
|
|
886
|
+
return fail(_method_not_found(method))
|
|
887
|
+
requested_level = log_levels.request_log_level(meta)
|
|
888
|
+
if requested_level is not None and not log_levels.is_logging_level(requested_level):
|
|
889
|
+
return fail(InvalidParams(f"Invalid request log level {requested_level!r}"))
|
|
890
|
+
if method == SUBSCRIPTIONS_LISTEN.name:
|
|
891
|
+
return self._listen(id, params, out)
|
|
892
|
+
if method == "prompts/list" and not self.server.has_registrations.prompts:
|
|
893
|
+
# An unadvertised capability's method (Effect's 2026-07-28 prompts/list).
|
|
894
|
+
return fail_with_http_status(404, _method_not_found_bare())
|
|
895
|
+
cacheable = method in _CACHEABLE
|
|
896
|
+
return self._feature(protocol, identity, method, params, out).map(
|
|
897
|
+
lambda result: complete(result, cacheable)
|
|
898
|
+
)
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
class _InputRequired(dict[str, Any]):
|
|
902
|
+
"""A result that is an ``input_required`` one."""
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
_NOT_ON_2026_07_28 = frozenset(
|
|
906
|
+
{"ping", "initialize", "logging/setLevel", "resources/subscribe", "resources/unsubscribe"}
|
|
907
|
+
)
|
|
908
|
+
"""Session methods ``_feature`` serves for the revisions that have sessions."""
|
|
909
|
+
|
|
910
|
+
_CACHEABLE = frozenset(
|
|
911
|
+
{"tools/list", "prompts/list", "resources/list", "resources/templates/list", "resources/read"}
|
|
912
|
+
)
|
|
913
|
+
"""The 2026-07-28 results carrying ``ttlMs`` and ``cacheScope`` (with
|
|
914
|
+
``server/discover``)."""
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
def _call_tool(
|
|
918
|
+
server: McpServerService,
|
|
919
|
+
protocol: AnyProtocolAdapter,
|
|
920
|
+
call: CallToolParams,
|
|
921
|
+
context: RequestContext,
|
|
922
|
+
) -> Effect[Wire, McpError, RequestServices]:
|
|
923
|
+
"""A tool's outcome on ``protocol`` 's wire. An unknown tool is always a
|
|
924
|
+
protocol error; the tool's own failures are ``isError`` results when the
|
|
925
|
+
revision reports them so (Effect's per-revision ``tools/call``), but a
|
|
926
|
+
retry the tool refuses (a tampered state) stays a protocol error."""
|
|
927
|
+
name = call["name"]
|
|
928
|
+
found = server.find_tool(name, context)
|
|
929
|
+
if found is None:
|
|
930
|
+
return fail(InvalidParams(f"Tool '{name}' not found"))
|
|
931
|
+
called = server.call_tool_for(call, context)
|
|
932
|
+
continued = context.input_responses is not None or context.request_state is not None
|
|
933
|
+
if protocol.reports_tool_errors_as_results:
|
|
934
|
+
|
|
935
|
+
def reported(
|
|
936
|
+
error: InvalidParams | InternalError,
|
|
937
|
+
) -> Effect[CallToolResult, InvalidParams]:
|
|
938
|
+
if continued and isinstance(error, InvalidParams):
|
|
939
|
+
return fail(error)
|
|
940
|
+
return succeed(
|
|
941
|
+
cast(
|
|
942
|
+
"CallToolResult",
|
|
943
|
+
{"isError": True, "content": [{"type": "text", "text": str(error)}]},
|
|
944
|
+
)
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
called = called.catch_all(reported)
|
|
948
|
+
|
|
949
|
+
def on_wire(outcome: CallToolResult | InputRequired) -> Effect[Wire, McpError]:
|
|
950
|
+
if isinstance(outcome, InputRequired):
|
|
951
|
+
if not protocol.is_stateless:
|
|
952
|
+
version = protocol.protocol_version
|
|
953
|
+
return fail(InvalidParams(f"Client input is not supported by MCP {version}"))
|
|
954
|
+
return _input_required(outcome, context.client_capabilities)
|
|
955
|
+
reason = projection.unsupported_content(protocol, outcome["content"])
|
|
956
|
+
if reason is not None:
|
|
957
|
+
return fail(InvalidParams(reason))
|
|
958
|
+
return succeed(projection.call_tool_result(protocol, outcome))
|
|
959
|
+
|
|
960
|
+
def checked(request: Option[ServerRequest]) -> Effect[Wire, McpError, RequestServices]:
|
|
961
|
+
if protocol.is_stateless and request is not None:
|
|
962
|
+
mismatch = param_headers.mismatch(
|
|
963
|
+
found.tool["inputSchema"], call.get("arguments"), request.value.headers
|
|
964
|
+
)
|
|
965
|
+
if mismatch is not None:
|
|
966
|
+
return fail_with_http_status(
|
|
967
|
+
400, ProtocolError(mismatch, error_code=HEADER_MISMATCH_ERROR_CODE)
|
|
968
|
+
)
|
|
969
|
+
return called.flat_map(on_wire)
|
|
970
|
+
|
|
971
|
+
return service_option(HttpServerRequest).flat_map(checked)
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def _input_required(
|
|
975
|
+
outcome: InputRequired, capabilities: ClientCapabilities
|
|
976
|
+
) -> Effect[Wire, McpError]:
|
|
977
|
+
"""``outcome`` on the 2026-07-28 wire, once the client can answer it."""
|
|
978
|
+
|
|
979
|
+
@gen
|
|
980
|
+
def run():
|
|
981
|
+
wire = _InputRequired()
|
|
982
|
+
requests = outcome.input_requests
|
|
983
|
+
required = reverse_policy.required_input_capabilities(requests, capabilities)
|
|
984
|
+
if required:
|
|
985
|
+
return (
|
|
986
|
+
yield from fail_with_http_status(
|
|
987
|
+
400,
|
|
988
|
+
ProtocolError(
|
|
989
|
+
"The request requires client capabilities that were not declared",
|
|
990
|
+
{"requiredCapabilities": required},
|
|
991
|
+
error_code=MISSING_REQUIRED_CLIENT_CAPABILITY_ERROR_CODE,
|
|
992
|
+
),
|
|
993
|
+
)
|
|
994
|
+
)
|
|
995
|
+
if requests is not None:
|
|
996
|
+
wire["inputRequests"] = yield from _decode_input_requests(requests).map_error(
|
|
997
|
+
lambda _: InternalError(_FEATURE_HANDLER_FAILED)
|
|
998
|
+
)
|
|
999
|
+
if outcome.request_state is not None:
|
|
1000
|
+
wire["requestState"] = outcome.request_state
|
|
1001
|
+
return wire
|
|
1002
|
+
|
|
1003
|
+
return run
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
def _wire(value: Mapping[str, Any]) -> Wire:
|
|
1007
|
+
return dict(value)
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
def _prompt_on_wire(
|
|
1011
|
+
protocol: AnyProtocolAdapter, identity: Identity, result: GetPromptResult | InputRequired
|
|
1012
|
+
) -> Effect[Wire, McpError]:
|
|
1013
|
+
if isinstance(result, InputRequired):
|
|
1014
|
+
if not protocol.is_stateless:
|
|
1015
|
+
return fail(InternalError(_FEATURE_HANDLER_FAILED))
|
|
1016
|
+
return _input_required(result, identity.capabilities)
|
|
1017
|
+
reason = projection.unsupported_content(protocol, [m["content"] for m in result["messages"]])
|
|
1018
|
+
if reason is not None:
|
|
1019
|
+
return fail(InvalidParams(reason))
|
|
1020
|
+
return succeed(projection.get_prompt_result(protocol, result))
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
_is_implementation = S.is_(IMPLEMENTATION)
|
|
1024
|
+
_decode_input_requests = S.decode_unknown_effect(INPUT_REQUESTS)
|
|
1025
|
+
|
|
1026
|
+
|
|
1027
|
+
def _extensions(
|
|
1028
|
+
protocol: AnyProtocolAdapter, extensions: Mapping[str, Any] | None
|
|
1029
|
+
) -> dict[str, Any]:
|
|
1030
|
+
"""The extension settings ``protocol`` can advertise: on 2026-07-28 only
|
|
1031
|
+
JSON objects, so a legacy scalar setting cannot break discovery."""
|
|
1032
|
+
if extensions is None:
|
|
1033
|
+
return {}
|
|
1034
|
+
if not protocol.is_stateless:
|
|
1035
|
+
return dict(extensions)
|
|
1036
|
+
return {name: value for name, value in extensions.items() if isinstance(value, dict)}
|
|
1037
|
+
|
|
1038
|
+
|
|
1039
|
+
def _offers_stateless(registry: Registry) -> bool:
|
|
1040
|
+
return any(p.is_stateless for p in registry.protocols)
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
def _unsupported_version(requested: str, registry: Registry) -> McpError:
|
|
1044
|
+
return ProtocolError(
|
|
1045
|
+
"Unsupported protocol version",
|
|
1046
|
+
{"requested": requested, "supported": list(registry.versions)},
|
|
1047
|
+
error_code=UNSUPPORTED_PROTOCOL_VERSION_ERROR_CODE,
|
|
1048
|
+
)
|