mcpscore 0.3.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.
- mcpscore/__init__.py +34 -0
- mcpscore/cli.py +64 -0
- mcpscore/enums.py +44 -0
- mcpscore/mcp_auditor.py +224 -0
- mcpscore/mcp_client.py +408 -0
- mcpscore/py.typed +0 -0
- mcpscore/rules/__init__.py +89 -0
- mcpscore/rules/base.py +229 -0
- mcpscore/rules/capabilities.py +398 -0
- mcpscore/rules/protocol_version.py +187 -0
- mcpscore/rules/registry.py +105 -0
- mcpscore/rules/security.py +277 -0
- mcpscore/rules/server_info.py +181 -0
- mcpscore/rules/tools.py +472 -0
- mcpscore/rules/transport.py +77 -0
- mcpscore-0.3.0.dist-info/METADATA +150 -0
- mcpscore-0.3.0.dist-info/RECORD +20 -0
- mcpscore-0.3.0.dist-info/WHEEL +4 -0
- mcpscore-0.3.0.dist-info/entry_points.txt +2 -0
- mcpscore-0.3.0.dist-info/licenses/LICENSE +21 -0
mcpscore/mcp_client.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
from contextlib import AsyncExitStack
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
from mcp import (
|
|
8
|
+
ClientSession,
|
|
9
|
+
InitializeResult,
|
|
10
|
+
ListPromptsResult,
|
|
11
|
+
ListResourcesResult,
|
|
12
|
+
ListToolsResult,
|
|
13
|
+
StdioServerParameters,
|
|
14
|
+
)
|
|
15
|
+
from mcp.client.sse import sse_client
|
|
16
|
+
from mcp.client.stdio import stdio_client
|
|
17
|
+
from mcp.client.streamable_http import streamable_http_client
|
|
18
|
+
from mcp.types import Prompt, Resource, Tool
|
|
19
|
+
|
|
20
|
+
from .enums import MCPTransportType
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
ERROR_NO_ACTIVE_SESSION = "No active session, connect to the MCP server first!"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class MCPClient:
|
|
28
|
+
"""Client for connecting to and communicating with MCP (Model Context Protocol) servers.
|
|
29
|
+
|
|
30
|
+
This class provides a high-level interface for:
|
|
31
|
+
- Establishing connections to MCP servers via various transport methods
|
|
32
|
+
- Initializing server sessions
|
|
33
|
+
- Listing available tools and resources
|
|
34
|
+
- Managing connection lifecycle and cleanup
|
|
35
|
+
|
|
36
|
+
Currently supports stdio transport for local server processes.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, timeout: int | None = None) -> None:
|
|
40
|
+
"""Initialize a new MCP client instance.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
timeout: Connection timeout in seconds (None for no timeout)
|
|
44
|
+
|
|
45
|
+
Sets up the client with an empty session and async exit stack for resource management.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
super().__init__()
|
|
49
|
+
self.session: ClientSession | None = None
|
|
50
|
+
self.exit_stack: AsyncExitStack = AsyncExitStack()
|
|
51
|
+
self.timeout: int | None = timeout
|
|
52
|
+
|
|
53
|
+
# Transport metadata (populated after connection)
|
|
54
|
+
self.transport_type: MCPTransportType | None = None
|
|
55
|
+
self.url: str | None = None
|
|
56
|
+
self.connection_time_ms: int | None = None
|
|
57
|
+
|
|
58
|
+
async def detect_and_connect(self, server_path_or_url: str) -> tuple[bool, MCPTransportType | None]:
|
|
59
|
+
"""Automatically detect transport type and connect to MCP server.
|
|
60
|
+
|
|
61
|
+
Attempts to connect using Streamable HTTP first, then falls back to SSE.
|
|
62
|
+
For local files (.py, .js), uses stdio transport.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
server_path_or_url: Path to server script or URL
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Tuple of (success: bool, transport: MCPTransportType | None)
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
# Check if it's a local file path
|
|
72
|
+
if server_path_or_url.endswith((".py", ".js")):
|
|
73
|
+
success = await self.connect_to_server(MCPTransportType.STDIO, server_path_or_url)
|
|
74
|
+
return (success, MCPTransportType.STDIO if success else None)
|
|
75
|
+
|
|
76
|
+
# Check if it's a URL
|
|
77
|
+
if server_path_or_url.startswith(("http://", "https://")):
|
|
78
|
+
# Try Streamable HTTP first
|
|
79
|
+
logger.info("Attempting Streamable HTTP connection...")
|
|
80
|
+
if await self.connect_to_server(MCPTransportType.STREAMABLE_HTTP, server_path_or_url):
|
|
81
|
+
return (True, MCPTransportType.STREAMABLE_HTTP)
|
|
82
|
+
|
|
83
|
+
# Fall back to SSE
|
|
84
|
+
logger.info("Streamable HTTP failed, trying SSE...")
|
|
85
|
+
if await self.connect_to_server(MCPTransportType.SSE, server_path_or_url):
|
|
86
|
+
return (True, MCPTransportType.SSE)
|
|
87
|
+
|
|
88
|
+
return (False, None)
|
|
89
|
+
|
|
90
|
+
logger.error("Invalid server path or URL: %s", server_path_or_url)
|
|
91
|
+
return (False, None)
|
|
92
|
+
|
|
93
|
+
async def connect_to_server(self, transport: MCPTransportType, server_path: str) -> bool:
|
|
94
|
+
"""Connect to an MCP server using the specified transport method.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
transport: The transport method to use (STDIO, STREAMABLE_HTTP, SSE)
|
|
98
|
+
server_path: Path to the server script file (.py or .js) for STDIO,
|
|
99
|
+
or URL for HTTP/SSE transports
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
True if a connection was successful, False otherwise
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
Logs errors for unsupported transport types or invalid server paths/URLs
|
|
106
|
+
|
|
107
|
+
"""
|
|
108
|
+
result: bool = False
|
|
109
|
+
|
|
110
|
+
match transport:
|
|
111
|
+
case MCPTransportType.STDIO:
|
|
112
|
+
result = await self._connect_with_stdio(server_path)
|
|
113
|
+
case MCPTransportType.STREAMABLE_HTTP:
|
|
114
|
+
result = await self._connect_with_streamable_http(server_path)
|
|
115
|
+
case MCPTransportType.SSE:
|
|
116
|
+
result = await self._connect_with_sse(server_path)
|
|
117
|
+
case _:
|
|
118
|
+
logger.error("This protocol is not supported: %s", transport)
|
|
119
|
+
|
|
120
|
+
return result
|
|
121
|
+
|
|
122
|
+
async def _connect_with_stdio(self, server_script_path: str) -> bool:
|
|
123
|
+
"""Establish a stdio connection to a local MCP server process.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
server_script_path: Path to the server script (.py or .js file)
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
True if a connection was successful, False otherwise
|
|
130
|
+
|
|
131
|
+
Note:
|
|
132
|
+
Automatically detects a script type and uses an appropriate launcher.
|
|
133
|
+
For Python scripts, uses sys.executable to ensure compatibility.
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
is_python: bool = server_script_path.endswith(".py")
|
|
137
|
+
is_js: bool = server_script_path.endswith(".js")
|
|
138
|
+
if not (is_python or is_js):
|
|
139
|
+
logger.error("Server script must be a .py or .js file")
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
# Use sys.executable for Python to ensure we use the same interpreter
|
|
143
|
+
command: str = sys.executable if is_python else "node"
|
|
144
|
+
server_params = StdioServerParameters(command=command, args=[server_script_path], env=None)
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
start_time = time.perf_counter()
|
|
148
|
+
self.stdio, self.write = await self.exit_stack.enter_async_context(stdio_client(server_params))
|
|
149
|
+
self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))
|
|
150
|
+
self.connection_time_ms = int((time.perf_counter() - start_time) * 1000)
|
|
151
|
+
|
|
152
|
+
# Store transport metadata
|
|
153
|
+
self.transport_type = MCPTransportType.STDIO
|
|
154
|
+
self.url = None # stdio doesn't have a URL
|
|
155
|
+
|
|
156
|
+
return True
|
|
157
|
+
except FileNotFoundError as e:
|
|
158
|
+
if is_python:
|
|
159
|
+
logger.exception("Python interpreter not found. Please ensure Python is installed and on PATH.")
|
|
160
|
+
else:
|
|
161
|
+
logger.exception("Node.js not found. Please ensure Node.js is installed and on PATH.")
|
|
162
|
+
logger.debug("Error details: %s", e)
|
|
163
|
+
return False
|
|
164
|
+
except PermissionError as e:
|
|
165
|
+
logger.exception("Permission denied accessing server script: %s", server_script_path)
|
|
166
|
+
logger.debug("Error details: %s", e)
|
|
167
|
+
return False
|
|
168
|
+
except Exception:
|
|
169
|
+
logger.exception("Failed to connect to MCP server")
|
|
170
|
+
return False
|
|
171
|
+
|
|
172
|
+
async def _connect_with_streamable_http(self, server_url: str) -> bool:
|
|
173
|
+
"""Establish HTTP connection to MCP server using streamable HTTP transport.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
server_url: Full URL to MCP server endpoint (e.g., https://server.com/mcp)
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
True if connection successful, False otherwise
|
|
180
|
+
|
|
181
|
+
Note:
|
|
182
|
+
- Requires HTTPS URL
|
|
183
|
+
- Implements automatic reconnection with exponential backoff
|
|
184
|
+
- Enforces connection timeout (15s) and total timeout (60s)
|
|
185
|
+
- Handles common HTTP errors (404, 500, connection refused, timeout)
|
|
186
|
+
|
|
187
|
+
"""
|
|
188
|
+
if not server_url.startswith(("http://", "https://")):
|
|
189
|
+
logger.error("Invalid URL format. Must start with http:// or https://")
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
# Configure HTTP client with timeouts and retries
|
|
194
|
+
client = httpx.AsyncClient(
|
|
195
|
+
timeout=httpx.Timeout(
|
|
196
|
+
connect=15.0, # Connection timeout: 15 seconds
|
|
197
|
+
read=60.0, # Read timeout: 60 seconds
|
|
198
|
+
write=30.0, # Write timeout: 30 seconds
|
|
199
|
+
pool=5.0, # Pool timeout: 5 seconds
|
|
200
|
+
),
|
|
201
|
+
follow_redirects=True,
|
|
202
|
+
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
# Establish connection using MCP SDK's streamable_http_client
|
|
206
|
+
start_time = time.perf_counter()
|
|
207
|
+
read_stream, write_stream, _session_id_callback = await self.exit_stack.enter_async_context(
|
|
208
|
+
streamable_http_client(server_url, http_client=client)
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
self.session = await self.exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
|
|
212
|
+
self.connection_time_ms = int((time.perf_counter() - start_time) * 1000)
|
|
213
|
+
|
|
214
|
+
# Store transport metadata
|
|
215
|
+
self.transport_type = MCPTransportType.STREAMABLE_HTTP
|
|
216
|
+
self.url = server_url
|
|
217
|
+
|
|
218
|
+
logger.info("Successfully connected to MCP server via Streamable HTTP: %s", server_url)
|
|
219
|
+
return True
|
|
220
|
+
|
|
221
|
+
except httpx.ConnectError as e:
|
|
222
|
+
logger.exception("Connection refused or server unreachable: %s", server_url)
|
|
223
|
+
logger.debug("Error details: %s", e)
|
|
224
|
+
return False
|
|
225
|
+
except httpx.TimeoutException as e:
|
|
226
|
+
logger.exception("Connection timeout for server: %s", server_url)
|
|
227
|
+
logger.debug("Error details: %s", e)
|
|
228
|
+
return False
|
|
229
|
+
except httpx.HTTPStatusError as e:
|
|
230
|
+
logger.exception("HTTP error %s from server: %s", e.response.status_code, server_url)
|
|
231
|
+
logger.debug("Error details: %s", e)
|
|
232
|
+
return False
|
|
233
|
+
except Exception:
|
|
234
|
+
logger.exception("Failed to connect to MCP server via Streamable HTTP")
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
async def _connect_with_sse(self, server_url: str) -> bool:
|
|
238
|
+
"""Establish SSE connection to MCP server.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
server_url: Full URL to MCP server SSE endpoint (e.g., https://server.com/sse)
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
True if connection successful, False otherwise
|
|
245
|
+
|
|
246
|
+
Note:
|
|
247
|
+
- Handles long-lived SSE connections
|
|
248
|
+
- Implements automatic reconnection (max 3 retries)
|
|
249
|
+
- Parses Server-Sent Events stream
|
|
250
|
+
- Manages keepalive/heartbeat
|
|
251
|
+
|
|
252
|
+
"""
|
|
253
|
+
if not server_url.startswith(("http://", "https://")):
|
|
254
|
+
logger.error("Invalid URL format. Must start with http:// or https://")
|
|
255
|
+
return False
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
# Configure HTTP client for SSE with appropriate timeouts
|
|
259
|
+
client = httpx.AsyncClient(
|
|
260
|
+
timeout=httpx.Timeout(
|
|
261
|
+
connect=15.0, # Connection timeout: 15 seconds
|
|
262
|
+
read=None, # No read timeout for streaming (handled by keepalive)
|
|
263
|
+
write=30.0, # Write timeout: 30 seconds
|
|
264
|
+
pool=5.0, # Pool timeout: 5 seconds
|
|
265
|
+
),
|
|
266
|
+
follow_redirects=True,
|
|
267
|
+
limits=httpx.Limits(max_connections=100, max_keepalive_connections=20),
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
# Establish connection using MCP SDK's sse_client
|
|
271
|
+
# Create a factory that ignores extra parameters since we already have a client
|
|
272
|
+
def client_factory(
|
|
273
|
+
headers: dict[str, str] | None = None,
|
|
274
|
+
timeout: httpx.Timeout | None = None,
|
|
275
|
+
auth: httpx.Auth | None = None,
|
|
276
|
+
) -> httpx.AsyncClient:
|
|
277
|
+
return client
|
|
278
|
+
|
|
279
|
+
start_time = time.perf_counter()
|
|
280
|
+
read_stream, write_stream = await self.exit_stack.enter_async_context(
|
|
281
|
+
sse_client(server_url, httpx_client_factory=client_factory)
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
self.session = await self.exit_stack.enter_async_context(ClientSession(read_stream, write_stream))
|
|
285
|
+
self.connection_time_ms = int((time.perf_counter() - start_time) * 1000)
|
|
286
|
+
|
|
287
|
+
# Store transport metadata
|
|
288
|
+
self.transport_type = MCPTransportType.SSE
|
|
289
|
+
self.url = server_url
|
|
290
|
+
|
|
291
|
+
logger.info("Successfully connected to MCP server via SSE: %s", server_url)
|
|
292
|
+
return True
|
|
293
|
+
|
|
294
|
+
except httpx.ConnectError as e:
|
|
295
|
+
logger.exception("Connection refused or server unreachable: %s", server_url)
|
|
296
|
+
logger.debug("Error details: %s", e)
|
|
297
|
+
return False
|
|
298
|
+
except httpx.TimeoutException as e:
|
|
299
|
+
logger.exception("Connection timeout for server: %s", server_url)
|
|
300
|
+
logger.debug("Error details: %s", e)
|
|
301
|
+
return False
|
|
302
|
+
except httpx.HTTPStatusError as e:
|
|
303
|
+
logger.exception("HTTP error %s from server: %s", e.response.status_code, server_url)
|
|
304
|
+
logger.debug("Error details: %s", e)
|
|
305
|
+
return False
|
|
306
|
+
except Exception:
|
|
307
|
+
logger.exception("Failed to connect to MCP server via SSE")
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
async def initialize(self) -> InitializeResult | None:
|
|
311
|
+
"""Initialize the MCP server session.
|
|
312
|
+
|
|
313
|
+
Performs the MCP handshake and retrieves server capabilities and information.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
InitializeResult containing server info, capabilities, and protocol version,
|
|
317
|
+
or None if initialization failed
|
|
318
|
+
|
|
319
|
+
Note:
|
|
320
|
+
Must be called after successfully connecting to a server
|
|
321
|
+
|
|
322
|
+
"""
|
|
323
|
+
if not self.session:
|
|
324
|
+
logger.error(ERROR_NO_ACTIVE_SESSION)
|
|
325
|
+
return None
|
|
326
|
+
|
|
327
|
+
try:
|
|
328
|
+
init_result: InitializeResult = await self.session.initialize()
|
|
329
|
+
return init_result
|
|
330
|
+
except Exception:
|
|
331
|
+
logger.exception("Failed to initialize MCP server")
|
|
332
|
+
return None
|
|
333
|
+
|
|
334
|
+
async def list_tools(self) -> list[Tool] | None:
|
|
335
|
+
"""List and display all available tools from the MCP server.
|
|
336
|
+
|
|
337
|
+
Retrieves the server's tools and logs detailed information about
|
|
338
|
+
each available tool, including name, description, and input schema.
|
|
339
|
+
|
|
340
|
+
Note:
|
|
341
|
+
Must be called after successfully initializing the server session
|
|
342
|
+
|
|
343
|
+
"""
|
|
344
|
+
if not self.session:
|
|
345
|
+
logger.error(ERROR_NO_ACTIVE_SESSION)
|
|
346
|
+
return None
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
response: ListToolsResult = await self.session.list_tools()
|
|
350
|
+
# TODO: Add support for nextCursor
|
|
351
|
+
return response.tools
|
|
352
|
+
except Exception:
|
|
353
|
+
logger.exception("Failed to list tools from the MCP server")
|
|
354
|
+
return None
|
|
355
|
+
|
|
356
|
+
async def list_resources(self) -> list[Resource] | None:
|
|
357
|
+
"""List and display all available resources from the MCP server.
|
|
358
|
+
|
|
359
|
+
Retrieves the server's resources
|
|
360
|
+
|
|
361
|
+
Note:
|
|
362
|
+
Must be called after successfully initializing the server session
|
|
363
|
+
|
|
364
|
+
"""
|
|
365
|
+
if not self.session:
|
|
366
|
+
logger.error(ERROR_NO_ACTIVE_SESSION)
|
|
367
|
+
return None
|
|
368
|
+
|
|
369
|
+
try:
|
|
370
|
+
response: ListResourcesResult = await self.session.list_resources()
|
|
371
|
+
# TODO: Add support for nextCursor
|
|
372
|
+
return response.resources
|
|
373
|
+
except Exception:
|
|
374
|
+
logger.exception("Failed to list resources from the MCP server")
|
|
375
|
+
return None
|
|
376
|
+
|
|
377
|
+
async def list_prompts(self) -> list[Prompt] | None:
|
|
378
|
+
"""List and display all available prompts from the MCP server.
|
|
379
|
+
|
|
380
|
+
Retrieves the server's prompts
|
|
381
|
+
|
|
382
|
+
Note:
|
|
383
|
+
Must be called after successfully initializing the server session
|
|
384
|
+
|
|
385
|
+
"""
|
|
386
|
+
if not self.session:
|
|
387
|
+
logger.error(ERROR_NO_ACTIVE_SESSION)
|
|
388
|
+
return None
|
|
389
|
+
|
|
390
|
+
try:
|
|
391
|
+
response: ListPromptsResult = await self.session.list_prompts()
|
|
392
|
+
# TODO: Add support for nextCursor
|
|
393
|
+
return response.prompts
|
|
394
|
+
except Exception:
|
|
395
|
+
logger.exception("Failed to list prompts from the MCP server")
|
|
396
|
+
return None
|
|
397
|
+
|
|
398
|
+
async def cleanup(self) -> None:
|
|
399
|
+
"""Clean up client resources and close all connections.
|
|
400
|
+
|
|
401
|
+
Properly closes the async exit stack, which will:
|
|
402
|
+
- Close the stdio transport
|
|
403
|
+
- Close the client session
|
|
404
|
+
- Clean up any other managed resources
|
|
405
|
+
|
|
406
|
+
Should be called when the client is no longer needed to prevent resource leaks.
|
|
407
|
+
"""
|
|
408
|
+
await self.exit_stack.aclose()
|
mcpscore/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""MCP audit rules package.
|
|
2
|
+
|
|
3
|
+
This package contains the rule system for MCP server auditing:
|
|
4
|
+
|
|
5
|
+
- BaseRule: Abstract base class for all audit rules
|
|
6
|
+
- RuleResult: Container for rule execution results
|
|
7
|
+
- RuleSeverity: Severity levels for rule classification
|
|
8
|
+
- AuditData: Container for server data used in audits
|
|
9
|
+
- RuleRegistry: Registry for managing and creating rules
|
|
10
|
+
- Specific rule implementations for protocol version and server info checks
|
|
11
|
+
|
|
12
|
+
The rule system is designed to be extensible, allowing easy addition of new
|
|
13
|
+
audit checks by implementing the BaseRule interface.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .base import (
|
|
17
|
+
AuditData,
|
|
18
|
+
BaseRule,
|
|
19
|
+
RuleResult,
|
|
20
|
+
RuleSeverity,
|
|
21
|
+
)
|
|
22
|
+
from .capabilities import (
|
|
23
|
+
CapabilityLoggingPresentRule,
|
|
24
|
+
CapabilityPromptsListChangedRule,
|
|
25
|
+
CapabilityPromptsPresentRule,
|
|
26
|
+
CapabilityResourcesListChangedRule,
|
|
27
|
+
CapabilityResourcesPresentRule,
|
|
28
|
+
CapabilityResourcesSubscribeRule,
|
|
29
|
+
CapabilityToolsListChangedRule,
|
|
30
|
+
)
|
|
31
|
+
from .protocol_version import (
|
|
32
|
+
AllowedVersionRule,
|
|
33
|
+
DeprecatedVersionRule,
|
|
34
|
+
LatestVersionRule,
|
|
35
|
+
)
|
|
36
|
+
from .registry import RuleRegistry, create_all_rules
|
|
37
|
+
from .security import (
|
|
38
|
+
ErrorDataLeakRule,
|
|
39
|
+
MalformedRequestHandlingRule,
|
|
40
|
+
TLSEnabledRule,
|
|
41
|
+
)
|
|
42
|
+
from .server_info import (
|
|
43
|
+
ServerNamePresentRule,
|
|
44
|
+
ServerTitlePresentRule,
|
|
45
|
+
ServerVersionPresentRule,
|
|
46
|
+
)
|
|
47
|
+
from .tools import (
|
|
48
|
+
ToolsAtLeastOneRule,
|
|
49
|
+
ToolsDescriptionPresentRule,
|
|
50
|
+
ToolsInputSchemaValidRule,
|
|
51
|
+
ToolsNamePresentRule,
|
|
52
|
+
ToolsOutputSchemaValidRule,
|
|
53
|
+
ToolsTitlePresentRule,
|
|
54
|
+
)
|
|
55
|
+
from .transport import (
|
|
56
|
+
SSETransportSupportRule,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
__all__ = (
|
|
60
|
+
"AllowedVersionRule",
|
|
61
|
+
"AuditData",
|
|
62
|
+
"BaseRule",
|
|
63
|
+
"CapabilityLoggingPresentRule",
|
|
64
|
+
"CapabilityPromptsListChangedRule",
|
|
65
|
+
"CapabilityPromptsPresentRule",
|
|
66
|
+
"CapabilityResourcesListChangedRule",
|
|
67
|
+
"CapabilityResourcesPresentRule",
|
|
68
|
+
"CapabilityResourcesSubscribeRule",
|
|
69
|
+
"CapabilityToolsListChangedRule",
|
|
70
|
+
"DeprecatedVersionRule",
|
|
71
|
+
"ErrorDataLeakRule",
|
|
72
|
+
"LatestVersionRule",
|
|
73
|
+
"MalformedRequestHandlingRule",
|
|
74
|
+
"RuleRegistry",
|
|
75
|
+
"RuleResult",
|
|
76
|
+
"RuleSeverity",
|
|
77
|
+
"SSETransportSupportRule",
|
|
78
|
+
"ServerNamePresentRule",
|
|
79
|
+
"ServerTitlePresentRule",
|
|
80
|
+
"ServerVersionPresentRule",
|
|
81
|
+
"TLSEnabledRule",
|
|
82
|
+
"ToolsAtLeastOneRule",
|
|
83
|
+
"ToolsDescriptionPresentRule",
|
|
84
|
+
"ToolsInputSchemaValidRule",
|
|
85
|
+
"ToolsNamePresentRule",
|
|
86
|
+
"ToolsOutputSchemaValidRule",
|
|
87
|
+
"ToolsTitlePresentRule",
|
|
88
|
+
"create_all_rules",
|
|
89
|
+
)
|