Repository navigation
Security triage: mcp 2.x, LangChain 1.x, Go floor, verified-token audience, npm lockfiles #432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
ca5618f
ecb7e25
0edd60d
4f8efcd
c8a9d03
82973b7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,8 +8,8 @@ | |
| from typing import Any, Dict | ||
|
|
||
| from langchain_openai import ChatOpenAI | ||
| from langchain.agents import AgentExecutor, create_tool_calling_agent | ||
| from langchain_core.prompts import ChatPromptTemplate | ||
| from langchain.agents import create_agent | ||
| from langchain_core.messages import HumanMessage | ||
| from langchain_core.tools import tool | ||
| from openai import AsyncOpenAI | ||
|
|
||
|
|
@@ -22,6 +22,32 @@ def tool_schema(spec): | |
| return getattr(spec, "input_schema", None) or getattr(spec, "inputSchema", {}) | ||
|
|
||
|
|
||
| # AgentExecutor counted model+tool iterations; a LangGraph agent counts graph | ||
| # steps, and one iteration is two of those. | ||
| MAX_ITERATIONS = 500 | ||
|
|
||
|
|
||
| async def run_round(agent, prompt): | ||
| """Drive one agent run to completion, echoing each turn as it happens. | ||
|
|
||
| Streaming the updates is what verbose=True used to give us: the tool calls | ||
| and their results are the whole point of a chaos run, so they are printed | ||
| as they arrive rather than summarised at the end. | ||
| """ | ||
| final = "" | ||
| async for step in agent.astream( | ||
| {"messages": [HumanMessage(content=prompt)]}, | ||
| config={"recursion_limit": 2 * MAX_ITERATIONS}, | ||
| stream_mode="updates", | ||
| ): | ||
| for update in step.values(): | ||
| for msg in (update or {}).get("messages", []): | ||
| msg.pretty_print() | ||
| if msg.type == "ai" and not getattr(msg, "tool_calls", None): | ||
| final = msg.content | ||
| return final | ||
|
|
||
|
|
||
| async def pick_model(inference_url, requested): | ||
| """Ask the mesh what it will serve this agent rather than guessing a name. | ||
|
|
||
|
|
@@ -101,27 +127,21 @@ async def mcp_tool(arguments_json: str) -> str: | |
| print(f"mesh offered model: {model}", file=sys.stderr) | ||
| llm = ChatOpenAI( | ||
| model=model, | ||
| openai_api_base=args.inference_url, | ||
| openai_api_key=args.auth if args.auth else "none", | ||
| base_url=args.inference_url, | ||
| api_key=args.auth if args.auth else "none", | ||
| default_headers=headers | ||
| ) | ||
|
|
||
| # Define the agent prompt | ||
| # No system turn: Gemma and several other instruction-tuned models | ||
| # reject the system role outright with a 400, and the mesh may hand | ||
| # this agent any model at all. The persona goes in the human turn, | ||
| # which every model accepts. | ||
| prompt_template = ChatPromptTemplate.from_messages([ | ||
| ("human", "You are an autonomous adversarial AI agent. You have access to tools.\n\n{input}"), | ||
| ("placeholder", "{agent_scratchpad}"), | ||
| ]) | ||
| prompt = f"You are an autonomous adversarial AI agent. You have access to tools.\n\n{args.prompt}" | ||
|
|
||
| # Create the LangChain Agent | ||
| # create_agent runs the model/tool loop until the model stops | ||
| # calling tools; it replaced AgentExecutor in LangChain 1.x. | ||
| print("Initializing LangChain Tool-Calling Agent...") | ||
| agent = create_tool_calling_agent(llm, lc_tools, prompt_template) | ||
|
|
||
| # AgentExecutor runs the ReAct/Tool loop automatically! | ||
| agent_executor = AgentExecutor(agent=agent, tools=lc_tools, verbose=True, max_iterations=500) | ||
| agent = create_agent(model=llm, tools=lc_tools) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| print(f"\n--- Starting Chaos Monkey Agent Loop ---") | ||
| print(f"Instruction: {args.prompt}\n") | ||
|
|
@@ -132,9 +152,9 @@ async def mcp_tool(arguments_json: str) -> str: | |
| print(f"\n--- Round {round_num} ---") | ||
| started = time.monotonic() | ||
| try: | ||
| result = await agent_executor.ainvoke({"input": args.prompt}) | ||
| output = await run_round(agent, prompt) | ||
| print("\n--- Final Agent Result ---") | ||
| print(result["output"]) | ||
| print(output) | ||
| except Exception as e: | ||
| # A crash is a data point, not a reason to stop: an agent | ||
| # that dies on the first refusal stops testing anything. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,10 +1,9 @@ | ||
| openai>=1.40,<3 | ||
| openai>=3.11.0,<4 | ||
| # Pinned to a major version: 1.x spells the transport streamablehttp_client and | ||
| # 2.x spells it streamable_http_client, so an unpinned floor silently changes | ||
| # the API underneath a sandbox image. | ||
| mcp>=2.0,<3 | ||
| # Pinned below 1.0 deliberately: LangChain 1.x removed AgentExecutor and | ||
| # create_tool_calling_agent from langchain.agents, so an unpinned install | ||
| # builds an image whose agent fails at import rather than at run time. | ||
| langchain>=0.3,<1 | ||
| langchain-openai>=0.2,<1 | ||
| mcp>=2.2.0,<3 | ||
| # LangChain 1.x replaced AgentExecutor with create_agent, which is what the | ||
| # agent is written against; 0.x no longer receives security fixes. | ||
| langchain>=1.4.0,<2 | ||
| langchain-openai>=1.6.2,<2 |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,7 +1,7 @@ | ||||||||||||||||
| """Calculator MCP backend exposed by node B in the local dev mesh.""" | ||||||||||||||||
| from mcp.server.fastmcp import FastMCP | ||||||||||||||||
| from mcp.server.mcpserver import MCPServer | ||||||||||||||||
|
|
||||||||||||||||
| mcp = FastMCP("calculator", host="0.0.0.0", port=7777) | ||||||||||||||||
| mcp = MCPServer("calculator") | ||||||||||||||||
|
Comment on lines
+2
to
+4
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no
Suggested change
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| @mcp.tool() | ||||||||||||||||
|
|
@@ -17,4 +17,4 @@ def multiply(a: float, b: float) -> float: | |||||||||||||||
|
|
||||||||||||||||
|
|
||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||
| mcp.run(transport="streamable-http") | ||||||||||||||||
| mcp.run(transport="streamable-http", host="0.0.0.0", port=7777) | ||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1 @@ | ||
| mcp[cli]>=1.0.0,<2.0.0 | ||
| mcp[cli]>=2.2.0,<3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| a2a-sdk>=1.0 | ||
| google-genai>=1.0 | ||
| httpx>=0.27 | ||
| sse-starlette>=2.0 | ||
| starlette>=0.40 | ||
| uvicorn>=0.30 | ||
| a2a-sdk>=1.1.2 | ||
| google-genai>=2.22.0 | ||
| httpx>=0.28.1 | ||
| sse-starlette>=3.4.11 | ||
| starlette>=1.6.0 | ||
| uvicorn>=0.52.4 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| FROM node:26-slim@sha256:14bf3eac4bf209d906d3c41256597d3ab1f926b2e93a79e9bdfe1efd32454239 | ||
|
|
||
| WORKDIR /srv | ||
| COPY package.json server.mjs lease-token.mjs ./ | ||
| RUN npm install | ||
| COPY package.json package-lock.json server.mjs lease-token.mjs ./ | ||
| RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund | ||
|
|
||
| EXPOSE 7780 | ||
| CMD ["node", "server.mjs"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is no
create_agentfunction inlangchain.agents. Based on the usage ofagent.astream(..., stream_mode="updates")and the argumentsmodel=llm, tools=lc_tools, you are actually using the LangGraph prebuilt ReAct agent. This requires importingcreate_react_agentfromlanggraph.prebuilt.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
langchain.agents.create_agentexists in LangChain 1.x (https://reference.langchain.com/python/langchain/agents/factory/create_agent) and is the replacement forAgentExecutor; it returns a compiled LangGraph graph, henceastream(..., stream_mode="updates").langgraph.prebuilt.create_react_agentis the older, now-deprecated path. Verified against langchain 1.4.1.