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) Waiting to run
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) Waiting to run
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
from mcp_types import (
|
||||
CallToolRequestParams,
|
||||
CallToolResult,
|
||||
ElicitRequest,
|
||||
ElicitRequestFormParams,
|
||||
ElicitResult,
|
||||
InputRequiredResult,
|
||||
ListToolsResult,
|
||||
PaginatedRequestParams,
|
||||
TextContent,
|
||||
Tool,
|
||||
)
|
||||
|
||||
from mcp.server import Server, ServerRequestContext
|
||||
|
||||
ASK_REGION = ElicitRequest(
|
||||
params=ElicitRequestFormParams(
|
||||
message="Which region should the database live in?",
|
||||
requested_schema={
|
||||
"type": "object",
|
||||
"properties": {"region": {"type": "string"}},
|
||||
"required": ["region"],
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult:
|
||||
return ListToolsResult(
|
||||
tools=[
|
||||
Tool(
|
||||
name="provision",
|
||||
description="Provision a database. Asks which region to put it in.",
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {"name": {"type": "string"}},
|
||||
"required": ["name"],
|
||||
},
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult | InputRequiredResult:
|
||||
answer = (params.input_responses or {}).get("region")
|
||||
if not isinstance(answer, ElicitResult) or answer.content is None:
|
||||
return InputRequiredResult(input_requests={"region": ASK_REGION}, request_state="provision-v1")
|
||||
name = (params.arguments or {})["name"]
|
||||
text = f"Provisioned {name!r} in {answer.content['region']}."
|
||||
return CallToolResult(content=[TextContent(type="text", text=text)])
|
||||
|
||||
|
||||
server = Server("Provisioner", on_list_tools=list_tools, on_call_tool=call_tool)
|
||||
@@ -0,0 +1,23 @@
|
||||
from mcp_types import CallToolResult, ElicitRequest, ElicitResult, InputRequest, InputRequiredResult, InputResponse
|
||||
|
||||
from mcp import Client
|
||||
|
||||
|
||||
def fulfil(request: InputRequest) -> InputResponse:
|
||||
if not isinstance(request, ElicitRequest):
|
||||
raise NotImplementedError(f"this client cannot answer a {request.method!r} request")
|
||||
return ElicitResult(action="accept", content={"region": "eu-west-1"})
|
||||
|
||||
|
||||
async def provision(client: Client, name: str) -> CallToolResult:
|
||||
result = await client.session.call_tool("provision", {"name": name}, allow_input_required=True)
|
||||
while isinstance(result, InputRequiredResult):
|
||||
responses = {key: fulfil(request) for key, request in (result.input_requests or {}).items()}
|
||||
result = await client.session.call_tool(
|
||||
"provision",
|
||||
{"name": name},
|
||||
input_responses=responses,
|
||||
request_state=result.request_state,
|
||||
allow_input_required=True,
|
||||
)
|
||||
return result
|
||||
@@ -0,0 +1,14 @@
|
||||
from mcp_types import ElicitRequestParams, ElicitResult
|
||||
|
||||
from mcp import Client
|
||||
from mcp.client import ClientRequestContext
|
||||
|
||||
|
||||
async def handle_elicitation(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult:
|
||||
return ElicitResult(action="accept", content={"region": "eu-west-1"})
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
async with Client("http://127.0.0.1:8000/mcp", elicitation_callback=handle_elicitation) as client:
|
||||
result = await client.call_tool("provision", {"name": "orders"})
|
||||
print(result.content)
|
||||
@@ -0,0 +1,26 @@
|
||||
from mcp_types import ElicitRequest, ElicitRequestFormParams, ElicitResult, InputRequiredResult
|
||||
|
||||
from mcp.server.mcpserver import Context, MCPServer
|
||||
from mcp.server.mcpserver.prompts.base import UserMessage
|
||||
|
||||
mcp = MCPServer("Briefing")
|
||||
|
||||
ASK_AUDIENCE = ElicitRequest(
|
||||
params=ElicitRequestFormParams(
|
||||
message="Who is the briefing for?",
|
||||
requested_schema={
|
||||
"type": "object",
|
||||
"properties": {"audience": {"type": "string"}},
|
||||
"required": ["audience"],
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@mcp.prompt()
|
||||
async def briefing(ctx: Context) -> list[UserMessage] | InputRequiredResult:
|
||||
"""Draft a briefing tuned to its audience."""
|
||||
answer = (ctx.input_responses or {}).get("audience")
|
||||
if not isinstance(answer, ElicitResult) or answer.content is None:
|
||||
return InputRequiredResult(input_requests={"audience": ASK_AUDIENCE})
|
||||
return [UserMessage(f"Write a briefing for {answer.content['audience']}.")]
|
||||
@@ -0,0 +1,38 @@
|
||||
import os
|
||||
|
||||
from cryptography.exceptions import InvalidTag
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
|
||||
from mcp.server import MCPServer
|
||||
from mcp.server.mcpserver import InvalidRequestState, RequestStateSecurity
|
||||
|
||||
PREFIX = "kms1." # format version; fed to GCM as associated data, so it is bound under the tag
|
||||
|
||||
|
||||
def unwrap_data_key() -> bytes:
|
||||
"""One KMS call at process start, kms.decrypt(CiphertextBlob=...); every token after that is local crypto."""
|
||||
return os.urandom(32) # stand-in for the unwrapped 32-byte data key
|
||||
|
||||
|
||||
class EnvelopeCodec:
|
||||
def __init__(self, data_key: bytes) -> None:
|
||||
self._aesgcm = AESGCM(data_key)
|
||||
|
||||
def seal(self, payload: bytes) -> str:
|
||||
nonce = os.urandom(12)
|
||||
return PREFIX + (nonce + self._aesgcm.encrypt(nonce, payload, PREFIX.encode())).hex()
|
||||
|
||||
def unseal(self, token: str) -> bytes:
|
||||
if not token.startswith(PREFIX):
|
||||
raise InvalidRequestState("unknown token format")
|
||||
body = token[len(PREFIX) :]
|
||||
try:
|
||||
raw = bytes.fromhex(body)
|
||||
if raw.hex() != body: # only the exact string seal() produced verifies
|
||||
raise ValueError("non-canonical hex")
|
||||
return self._aesgcm.decrypt(raw[:12], raw[12:], PREFIX.encode())
|
||||
except (ValueError, InvalidTag) as exc:
|
||||
raise InvalidRequestState("token failed verification") from exc
|
||||
|
||||
|
||||
mcp = MCPServer("Deployer", request_state_security=RequestStateSecurity(codec=EnvelopeCodec(unwrap_data_key())))
|
||||
Reference in New Issue
Block a user