mirror of
https://github.com/msoedov/agentic_security.git
synced 2026-06-23 21:59:57 +02:00
Merge pull request #300 from JackSpiece/fix/mcp-client-usage-examples
docs: add MCP client usage examples
This commit is contained in:
@@ -83,6 +83,25 @@ agentic_security --port=PORT --host=HOST
|
||||
|
||||
<img width="100%" alt="booking-screen" src="https://raw.githubusercontent.com/msoedov/agentic_security/refs/heads/main/docs/images/demo.gif">
|
||||
|
||||
## MCP client example
|
||||
|
||||
Agentic Security includes an MCP stdio server in `agentic_security.mcp.main`.
|
||||
To list the available MCP tools from a local checkout:
|
||||
|
||||
```shell
|
||||
python examples/mcp_client_usage.py
|
||||
```
|
||||
|
||||
To call HTTP-backed tools, run the Agentic Security app first, then point the
|
||||
MCP server at it:
|
||||
|
||||
```shell
|
||||
agentic_security --host 127.0.0.1 --port 8718
|
||||
python examples/mcp_client_usage.py --agentic-security-url http://127.0.0.1:8718 --call get_spec_templates
|
||||
```
|
||||
|
||||
See `docs/mcp_client_usage.md` for the full walkthrough.
|
||||
|
||||
## LLM kwargs
|
||||
|
||||
Agentic Security uses plain text HTTP spec like:
|
||||
|
||||
@@ -1,30 +1,32 @@
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
from agentic_security.logutils import logger
|
||||
|
||||
# Create server parameters for stdio connection
|
||||
server_params = StdioServerParameters(
|
||||
command="python", # Executable
|
||||
args=["agentic_security/mcp/main.py"], # Your server script
|
||||
env=None, # Optional environment variables
|
||||
)
|
||||
|
||||
def build_server_params() -> StdioServerParameters:
|
||||
"""Create server parameters for a stdio MCP client session."""
|
||||
return StdioServerParameters(
|
||||
command=sys.executable,
|
||||
args=["-m", "agentic_security.mcp.main"],
|
||||
env=None,
|
||||
)
|
||||
|
||||
|
||||
async def run() -> None:
|
||||
try:
|
||||
server_params = build_server_params()
|
||||
logger.info(
|
||||
"Starting stdio client session with server parameters: %s", server_params
|
||||
)
|
||||
async with stdio_client(server_params) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
# Initialize the connection --> connection does not work
|
||||
logger.info("Initializing client session...")
|
||||
await session.initialize()
|
||||
|
||||
# List available prompts, resources, and tools --> no avalialbe tools
|
||||
logger.info("Listing available prompts...")
|
||||
prompts = await session.list_prompts()
|
||||
logger.info(f"Available prompts: {prompts}")
|
||||
@@ -36,26 +38,10 @@ async def run() -> None:
|
||||
logger.info("Listing available tools...")
|
||||
tools = await session.list_tools()
|
||||
logger.info(f"Available tools: {tools}")
|
||||
|
||||
# Call the echo tool --> echo tool issue
|
||||
logger.info("Calling echo_tool with message...")
|
||||
echo_result = await session.call_tool(
|
||||
"echo_tool", arguments={"message": "Hello from client!"}
|
||||
logger.info(
|
||||
"Available MCP tool names: %s",
|
||||
", ".join(tool.name for tool in tools.tools),
|
||||
)
|
||||
logger.info(f"Tool result: {echo_result}")
|
||||
|
||||
# # Read the echo resource
|
||||
# echo_content, mime_type = await session.read_resource(
|
||||
# "echo://Hello_resource"
|
||||
# )
|
||||
# logger.info(f"Resource content: {echo_content}")
|
||||
# logger.info(f"Resource MIME type: {mime_type}")
|
||||
|
||||
# # Get and use the echo prompt
|
||||
# prompt_result = await session.get_prompt(
|
||||
# "echo_prompt", arguments={"message": "Hello prompt!"}
|
||||
# )
|
||||
# logger.info(f"Prompt result: {prompt_result}")
|
||||
|
||||
logger.info("Client operations completed successfully.")
|
||||
return prompts, resources, tools
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
import httpx
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
@@ -8,7 +10,7 @@ mcp = FastMCP(
|
||||
)
|
||||
|
||||
# FastAPI Server Configuration
|
||||
AGENTIC_SECURITY = "http://0.0.0.0:8718"
|
||||
AGENTIC_SECURITY = os.getenv("AGENTIC_SECURITY_URL", "http://0.0.0.0:8718")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# MCP client usage
|
||||
|
||||
Agentic Security exposes an MCP stdio server in `agentic_security.mcp.main`.
|
||||
The example client in `examples/mcp_client_usage.py` shows how to connect to
|
||||
that server, list available tools, and optionally call simple no-argument tools.
|
||||
|
||||
## List MCP tools
|
||||
|
||||
From the repository root:
|
||||
|
||||
```shell
|
||||
python examples/mcp_client_usage.py
|
||||
```
|
||||
|
||||
This starts the MCP server as a subprocess with:
|
||||
|
||||
```shell
|
||||
python -m agentic_security.mcp.main
|
||||
```
|
||||
|
||||
The client initializes an MCP session and prints the available Agentic Security
|
||||
tools, including `verify_llm`, `start_scan`, `stop_scan`, `get_data_config`, and
|
||||
`get_spec_templates`.
|
||||
|
||||
## Call an HTTP-backed tool
|
||||
|
||||
Some MCP tools call the Agentic Security HTTP app. Start the app in another
|
||||
terminal first:
|
||||
|
||||
```shell
|
||||
agentic_security --host 127.0.0.1 --port 8718
|
||||
```
|
||||
|
||||
Then point the MCP server at that app and call a no-argument tool:
|
||||
|
||||
```shell
|
||||
python examples/mcp_client_usage.py \
|
||||
--agentic-security-url http://127.0.0.1:8718 \
|
||||
--call get_spec_templates
|
||||
```
|
||||
|
||||
You can also set `AGENTIC_SECURITY_URL` directly:
|
||||
|
||||
```shell
|
||||
AGENTIC_SECURITY_URL=http://127.0.0.1:8718 python examples/mcp_client_usage.py --call get_data_config
|
||||
```
|
||||
|
||||
## Use the package helper
|
||||
|
||||
For tests or quick local checks, `agentic_security.mcp.client.run()` creates the
|
||||
same stdio session and returns the prompt, resource, and tool list results:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agentic_security.mcp.client import run
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
_prompts, _resources, tools = await run()
|
||||
print([tool.name for tool in tools.tools])
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
@@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Example MCP client for the Agentic Security stdio server.
|
||||
|
||||
The default command lists the tools exposed by ``agentic_security.mcp.main``.
|
||||
If the Agentic Security HTTP app is running, pass ``--call`` to invoke one of
|
||||
the no-argument HTTP-backed tools through MCP.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
|
||||
NO_ARGUMENT_TOOLS = {"get_data_config", "get_spec_templates", "stop_scan"}
|
||||
|
||||
|
||||
def _build_server_params(agentic_security_url: str | None) -> StdioServerParameters:
|
||||
env = os.environ.copy()
|
||||
if agentic_security_url:
|
||||
env["AGENTIC_SECURITY_URL"] = agentic_security_url
|
||||
|
||||
return StdioServerParameters(
|
||||
command=sys.executable,
|
||||
args=["-m", "agentic_security.mcp.main"],
|
||||
env=env,
|
||||
)
|
||||
|
||||
|
||||
def _jsonable(value: Any) -> Any:
|
||||
if hasattr(value, "model_dump"):
|
||||
return value.model_dump(mode="json")
|
||||
if isinstance(value, (list, tuple)):
|
||||
return [_jsonable(item) for item in value]
|
||||
if isinstance(value, dict):
|
||||
return {key: _jsonable(item) for key, item in value.items()}
|
||||
return value
|
||||
|
||||
|
||||
async def run_client(agentic_security_url: str | None, call_tool: str | None) -> None:
|
||||
server_params = _build_server_params(agentic_security_url)
|
||||
|
||||
async with stdio_client(server_params) as (read, write):
|
||||
async with ClientSession(read, write) as session:
|
||||
await session.initialize()
|
||||
tools = await session.list_tools()
|
||||
tool_names = [tool.name for tool in tools.tools]
|
||||
|
||||
print("Available Agentic Security MCP tools:")
|
||||
for tool in tools.tools:
|
||||
description_lines = (tool.description or "").strip().splitlines()
|
||||
description = description_lines[0] if description_lines else "No description"
|
||||
print(f"- {tool.name}: {description}")
|
||||
|
||||
if not call_tool:
|
||||
return
|
||||
|
||||
if call_tool not in tool_names:
|
||||
raise ValueError(
|
||||
f"Unknown tool {call_tool!r}. Available tools: {', '.join(tool_names)}"
|
||||
)
|
||||
if call_tool not in NO_ARGUMENT_TOOLS:
|
||||
raise ValueError(
|
||||
f"{call_tool!r} requires arguments. This example only calls "
|
||||
f"no-argument tools: {', '.join(sorted(NO_ARGUMENT_TOOLS))}"
|
||||
)
|
||||
|
||||
result = await session.call_tool(call_tool, arguments={})
|
||||
print()
|
||||
print(f"{call_tool} result:")
|
||||
print(json.dumps(_jsonable(result), indent=2))
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="List Agentic Security MCP tools and optionally call one.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--agentic-security-url",
|
||||
default=None,
|
||||
help=(
|
||||
"Agentic Security HTTP app URL. Defaults to AGENTIC_SECURITY_URL "
|
||||
"or http://0.0.0.0:8718 in the server."
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--call",
|
||||
choices=sorted(NO_ARGUMENT_TOOLS),
|
||||
help="Optional no-argument MCP tool to call after listing tools.",
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
asyncio.run(run_client(args.agentic_security_url, args.call))
|
||||
@@ -26,6 +26,7 @@ nav:
|
||||
- Dataset Extension: datasets.md
|
||||
- External Modules: external_module.md
|
||||
- CI/CD Integration: ci_cd.md
|
||||
- MCP Client Usage: mcp_client_usage.md
|
||||
- Bayesian Optimization: optimizer.md
|
||||
- Image Generation: image_generation.md
|
||||
- Stenography Functions: stenography.md
|
||||
|
||||
+13
-5
@@ -4,9 +4,17 @@ from agentic_security.mcp.client import run
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_echo_tool():
|
||||
"""Test the echo tool functionality"""
|
||||
async def test_mcp_client_lists_agentic_security_tools():
|
||||
"""Test that the MCP client can discover the server tools."""
|
||||
prompts, resources, tools = await run()
|
||||
assert prompts
|
||||
assert resources
|
||||
assert tools
|
||||
tool_names = {tool.name for tool in tools.tools}
|
||||
|
||||
assert prompts is not None
|
||||
assert resources is not None
|
||||
assert {
|
||||
"verify_llm",
|
||||
"start_scan",
|
||||
"stop_scan",
|
||||
"get_data_config",
|
||||
"get_spec_templates",
|
||||
}.issubset(tool_names)
|
||||
|
||||
Reference in New Issue
Block a user