Skip to content

Commit 0967e2a

Browse files
committed
feat(prompts_sample): add sample for mcp prompts
1 parent f19e99b commit 0967e2a

22 files changed

+2254
-4
lines changed

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,8 @@ url = "https://test.pypi.org/simple/"
122122
publish-url = "https://test.pypi.org/legacy/"
123123
explicit = true
124124

125+
[tool.uv.workspace]
126+
members = [
127+
"samples/mcp-refactoring-assistant",
128+
]
129+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ANTHROPIC_API_KEY=***
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Agent Code Patterns Reference
2+
3+
This document provides practical code patterns for building UiPath coded agents using LangGraph and the UiPath Python SDK.
4+
5+
---
6+
7+
## Documentation Structure
8+
9+
This documentation is split into multiple files for efficient context loading. Load only the files you need:
10+
11+
1. **@.agent/REQUIRED_STRUCTURE.md** - Agent structure patterns and templates
12+
- **When to load:** Creating a new agent or understanding required patterns
13+
- **Contains:** Required Pydantic models (Input, State, Output), LLM initialization patterns, standard agent template
14+
15+
2. **@.agent/SDK_REFERENCE.md** - Complete SDK API reference
16+
- **When to load:** Calling UiPath SDK methods, working with services (actions, assets, jobs, etc.)
17+
- **Contains:** All SDK services and methods with full signatures and type annotations
18+
19+
3. **@.agent/CLI_REFERENCE.md** - CLI commands documentation
20+
- **When to load:** Working with `uipath init`, `uipath run`, or `uipath eval` commands
21+
- **Contains:** Command syntax, options, usage examples, and workflows
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
flowchart TB
2+
__start__(__start__)
3+
agent(agent)
4+
prompt(prompt)
5+
__end__(__end__)
6+
__start__ --> agent
7+
agent --> prompt
8+
prompt --> __end__
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"version": "2.0",
3+
"resources": []
4+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
{
2+
"$schema": "https://cloud.uipath.com/draft/2024-12/entry-point",
3+
"$id": "entry-points.json",
4+
"entryPoints": [
5+
{
6+
"filePath": "agent",
7+
"uniqueId": "f8d40b39-3346-4818-8ff3-ab3709001966",
8+
"type": "agent",
9+
"input": {
10+
"type": "object",
11+
"properties": {
12+
"code": {
13+
"title": "Code",
14+
"type": "string"
15+
},
16+
"messages": {
17+
"items": {},
18+
"title": "Messages",
19+
"type": "array"
20+
},
21+
"prompt_name": {
22+
"title": "Prompt Name",
23+
"type": "string"
24+
},
25+
"prompt_arguments": {
26+
"additionalProperties": true,
27+
"title": "Prompt Arguments",
28+
"type": "object"
29+
}
30+
},
31+
"required": []
32+
},
33+
"output": {
34+
"type": "object",
35+
"properties": {
36+
"code": {
37+
"default": null,
38+
"title": "Code",
39+
"type": "string"
40+
},
41+
"messages": {
42+
"default": null,
43+
"items": {},
44+
"title": "Messages",
45+
"type": "array"
46+
},
47+
"prompt_name": {
48+
"default": null,
49+
"title": "Prompt Name",
50+
"type": "string"
51+
},
52+
"prompt_arguments": {
53+
"additionalProperties": true,
54+
"default": null,
55+
"title": "Prompt Arguments",
56+
"type": "object"
57+
}
58+
},
59+
"required": []
60+
},
61+
"graph": {
62+
"nodes": [
63+
{
64+
"id": "__start__",
65+
"name": "__start__",
66+
"type": "__start__",
67+
"subgraph": null,
68+
"metadata": {}
69+
},
70+
{
71+
"id": "agent",
72+
"name": "agent",
73+
"type": "node",
74+
"subgraph": null,
75+
"metadata": {}
76+
},
77+
{
78+
"id": "prompt",
79+
"name": "prompt",
80+
"type": "node",
81+
"subgraph": null,
82+
"metadata": {}
83+
},
84+
{
85+
"id": "__end__",
86+
"name": "__end__",
87+
"type": "__end__",
88+
"subgraph": null,
89+
"metadata": {}
90+
}
91+
],
92+
"edges": [
93+
{
94+
"source": "__start__",
95+
"target": "agent",
96+
"label": null
97+
},
98+
{
99+
"source": "agent",
100+
"target": "prompt",
101+
"label": null
102+
},
103+
{
104+
"source": "prompt",
105+
"target": "__end__",
106+
"label": null
107+
}
108+
]
109+
}
110+
}
111+
]
112+
}
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
"""LangGraph agent for Code Refactoring Assistant.
2+
"""
3+
4+
import json
5+
import sys
6+
from contextlib import asynccontextmanager
7+
from pathlib import Path
8+
from typing import Any, Optional
9+
10+
from langchain_anthropic import ChatAnthropic
11+
from langchain_core.messages import HumanMessage
12+
from langchain_mcp_adapters.client import MultiServerMCPClient
13+
from langgraph.graph import END, START, StateGraph
14+
from langgraph.prebuilt import create_react_agent
15+
from typing_extensions import TypedDict
16+
17+
model = ChatAnthropic(
18+
model_name="claude-3-7-sonnet-latest",
19+
timeout=60,
20+
stop=None
21+
)
22+
23+
server_path = Path(__file__).parent / "server.py"
24+
25+
26+
def _try_parse_json(value: Any) -> Optional[dict]:
27+
"""Robustly parse JSON from various formats (dict, str, list)."""
28+
if value is None:
29+
return None
30+
31+
if isinstance(value, dict):
32+
if 'text' in value:
33+
return _try_parse_json(value['text'])
34+
if 'prompt_name' in value or 'error' in value:
35+
return value
36+
return None
37+
38+
if isinstance(value, str):
39+
value = value.strip()
40+
if not value:
41+
return None
42+
try:
43+
parsed = json.loads(value)
44+
return parsed if isinstance(parsed, dict) else None
45+
except json.JSONDecodeError:
46+
return None
47+
48+
if isinstance(value, list):
49+
for item in value:
50+
parsed = _try_parse_json(item)
51+
if parsed:
52+
return parsed
53+
54+
return None
55+
56+
57+
class InputSchema(TypedDict):
58+
"""Input schema: the code to analyze."""
59+
code: str
60+
61+
62+
class OutputSchema(TypedDict):
63+
"""Output schema: just the refactoring result."""
64+
result: str
65+
66+
67+
class GraphState(TypedDict, total=False):
68+
"""Internal state for the graph (not exposed to user)."""
69+
code: str
70+
messages: list
71+
prompt_name: str
72+
prompt_arguments: dict
73+
result: str
74+
75+
76+
@asynccontextmanager
77+
async def make_graph():
78+
"""Create the refactoring assistant graph with MCP client."""
79+
# Initialize MCP client
80+
client = MultiServerMCPClient({
81+
"code-refactoring": {
82+
"command": sys.executable,
83+
"args": [str(server_path)],
84+
"transport": "stdio",
85+
},
86+
})
87+
88+
tools = await client.get_tools()
89+
react_agent = create_react_agent(model, tools)
90+
91+
async def agent_node(state: dict) -> GraphState:
92+
"""Agent analyzes code and determines which prompt to use."""
93+
code = state.get('code', '')
94+
initial_msg = HumanMessage(
95+
content=f"""You are a refactoring assistant.
96+
97+
1) Analyze this code using analyze_code_complexity
98+
2) Detect issues using detect_code_smells
99+
3) Call get_refactoring_guide with:
100+
- issue_type: the main issue detected
101+
- code: the code to refactor
102+
- complexity_info: results from step 1
103+
- smells_info: results from step 2
104+
105+
The get_refactoring_guide tool will return {{\"prompt_name\": \"...\", \"arguments\": {{...}}}} ready for the next step.
106+
107+
CODE:
108+
{code}
109+
"""
110+
)
111+
112+
result = await react_agent.ainvoke({"messages": [initial_msg]})
113+
114+
prompt_name = ""
115+
prompt_args: dict = {}
116+
117+
for msg in result.get("messages", []):
118+
if hasattr(msg, 'type') and msg.type == 'tool':
119+
tool_name = getattr(msg, 'name', None) or getattr(msg, 'tool', None)
120+
if tool_name == 'get_refactoring_guide':
121+
data = _try_parse_json(getattr(msg, 'content', None))
122+
if data and 'prompt_name' in data:
123+
prompt_name = data['prompt_name']
124+
prompt_args = data.get('arguments', {}) or {}
125+
break
126+
127+
if not prompt_name:
128+
return {
129+
"prompt_name": "",
130+
"prompt_arguments": {},
131+
}
132+
133+
prompt_args.setdefault('code', code)
134+
135+
return {
136+
"prompt_name": prompt_name,
137+
"prompt_arguments": prompt_args,
138+
}
139+
140+
async def prompt_node(state: GraphState) -> dict:
141+
"""Fetch prompt using client.get_prompt() and generate final response."""
142+
prompt_name = state.get("prompt_name", "")
143+
144+
if not prompt_name:
145+
return {
146+
"result": "Unable to determine appropriate refactoring prompt. "
147+
"Please ensure the agent analyzed the code and called get_refactoring_guide."
148+
}
149+
150+
prompt_messages = await client.get_prompt(
151+
"code-refactoring",
152+
prompt_name,
153+
arguments=state.get("prompt_arguments", {})
154+
)
155+
156+
final_response = await model.ainvoke(prompt_messages)
157+
158+
return {"result": final_response.content}
159+
160+
builder = StateGraph(GraphState, input=InputSchema, output=OutputSchema)
161+
builder.add_node("agent", agent_node)
162+
builder.add_node("prompt", prompt_node)
163+
164+
builder.add_edge(START, "agent")
165+
builder.add_edge("agent", "prompt")
166+
builder.add_edge("prompt", END)
167+
168+
yield builder.compile()
169+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"code": "def process_data(x, y, z):\n if x:\n if y:\n if z:\n return x + y + z"
3+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"dependencies": ["."],
3+
"graphs": {
4+
"agent": "./graph.py:make_graph"
5+
},
6+
"env": ".env"
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def main():
2+
print("Hello from mcp-refactoring-assistant!")
3+
4+
5+
if __name__ == "__main__":
6+
main()

0 commit comments

Comments
 (0)