chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Has been cancelled
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
"""A real low-level Server over the stdio transport, for the suite's one subprocess test.
|
||||
|
||||
Runnable as `python -m tests.interaction.transports._stdio_server` from the repo root; the test
|
||||
launches it that way via `stdio_client`. Kept separate from the test module so the server lives in
|
||||
its own importable file (subprocess coverage applies) while the test file follows the suite's
|
||||
test-only-functions convention.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import anyio
|
||||
import coverage
|
||||
from mcp_types import (
|
||||
CallToolRequestParams,
|
||||
CallToolResult,
|
||||
EmptyResult,
|
||||
ListToolsResult,
|
||||
PaginatedRequestParams,
|
||||
SetLevelRequestParams,
|
||||
TextContent,
|
||||
Tool,
|
||||
)
|
||||
|
||||
from mcp.server import Server, ServerRequestContext
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.shared.exceptions import MCPDeprecationWarning
|
||||
|
||||
|
||||
async def list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult:
|
||||
return ListToolsResult(
|
||||
tools=[
|
||||
Tool(
|
||||
name="echo",
|
||||
input_schema={"type": "object", "properties": {"text": {"type": "string"}}, "required": ["text"]},
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
|
||||
assert params.name == "echo"
|
||||
assert params.arguments is not None
|
||||
text = params.arguments["text"]
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", MCPDeprecationWarning)
|
||||
await ctx.session.send_log_message(level="info", data=f"echoing {text}", logger="echo") # pyright: ignore[reportDeprecated]
|
||||
return CallToolResult(content=[TextContent(text=text)])
|
||||
|
||||
|
||||
async def set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult:
|
||||
"""Registered so the logging capability is advertised; the client never sets a level."""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore", MCPDeprecationWarning)
|
||||
server = Server( # pyright: ignore[reportDeprecated]
|
||||
"stdio-echo", on_list_tools=list_tools, on_call_tool=call_tool, on_set_logging_level=set_logging_level
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await server.run(read_stream, write_stream, server.create_initialization_options())
|
||||
# Flush this process's coverage data before the clean-exit line below. Without this, the
|
||||
# data is only written by coverage's atexit hook during interpreter teardown -- and on a
|
||||
# slow Windows runner that can overrun the transport's termination grace, so the kill
|
||||
# silently destroys the data file and the 100% gate trips on this module's subprocess-only
|
||||
# lines. Saving here puts the write before the line the test synchronizes on: once the
|
||||
# parent has seen "clean exit", the data is durably on disk and the escalation is harmless.
|
||||
# Nothing measured may execute after the save (it would be unrecordable by construction),
|
||||
# hence the excluded lines below. The branch is pragma'd because under coverage the
|
||||
# instance always exists, and without coverage nothing is measured anyway.
|
||||
cov = getattr(coverage.process_startup, "coverage", None)
|
||||
if cov is not None: # pragma: no branch
|
||||
# stop() is load-bearing twice over: it ends tracing, making itself the last
|
||||
# recordable line, and it leaves nothing new for coverage's atexit re-save to flush --
|
||||
# so a kill landing during interpreter teardown cannot corrupt the file save() wrote
|
||||
# (coverage opens it with sqlite journaling off; a torn rewrite would not roll back).
|
||||
cov.stop()
|
||||
cov.save() # pragma: lax no cover - untraced: stop() above already ended measurement
|
||||
# Reached only when the run loop exits because stdin closed; if the process were terminated
|
||||
# the test's stderr capture would not see this line. lax no cover: runs after the coverage
|
||||
# save by design, so it can never appear covered.
|
||||
print("stdio-echo: clean exit", file=sys.stderr, flush=True) # pragma: lax no cover
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
anyio.run(main)
|
||||
Reference in New Issue
Block a user