Skip to content

Commit fd78e75

Browse files
committed
got the resources, prompts and tools working
1 parent fa425ea commit fd78e75

4 files changed

Lines changed: 234 additions & 37 deletions

File tree

fw_ex/praw_spiked/mcp-reddit-server/client.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,23 +66,28 @@ async def connect_to_server(self, server_script_path: str):
6666
# listing available prompts
6767
response = await self.session.list_prompts()
6868
prompts = response.prompts
69+
70+
test_text = await self.session.get_prompt(
71+
"reply_with_context", arguments={"query": "test", "context": "test_context"}
72+
)
73+
print(test_text.messages[0].content.text)
6974
print("\nAvailable prompts:", [prompt.name for prompt in prompts])
7075

7176
async def process_query(self, query: str) -> str:
7277
"""Process a query using Claude and available tools"""
7378
# get the tools
7479
response = await self.session.list_tools()
75-
# get the resources
76-
avbl_data = await self.session.read_resource("subreddit://info")
77-
# use the resources in the prompt
78-
context_prompt = await self.session.get_prompt(
79-
"reply_with_context",
80-
arguments={"context": avbl_data.contents[0].text, "query": query},
81-
)
82-
query_with_context = context_prompt.messages[0].content
83-
print(query_with_context.text)
80+
# # get the resources
81+
# avbl_data = await self.session.read_resource("subreddit://info")
82+
# # use the resources in the prompt
83+
# context_prompt = await self.session.get_prompt(
84+
# "reply_with_context",
85+
# arguments={"context": avbl_data.contents[0].text, "query": query},
86+
# )
87+
# query_with_context = context_prompt.messages[0].content
88+
# print(query_with_context.text)
8489
# build it into the message list
85-
messages = [{"role": "user", "content": query_with_context.text}]
90+
messages = [{"role": "user", "content": query}]
8691
available_tools = [
8792
{
8893
"name": tool.name,
@@ -115,7 +120,7 @@ async def process_query(self, query: str) -> str:
115120
result = await self.session.call_tool(tool_name, tool_args)
116121
tool_results.append({"call": tool_name, "result": result})
117122
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
118-
123+
print(f"Toolcall Result: {result.content}")
119124
# Continue conversation with tool results
120125
if hasattr(content, "text") and content.text:
121126
messages.append({"role": "assistant", "content": content.text})
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import asyncio
2+
from typing import Optional
3+
from contextlib import AsyncExitStack
4+
from inspect import getsource
5+
6+
# pyright: reportMissingImports=false
7+
# pyright: reportOptionalSubscript=false
8+
# pyright: reportOptionalMemberAccess=false
9+
10+
from mcp import ClientSession, StdioServerParameters
11+
from mcp.client.stdio import stdio_client
12+
13+
from anthropic import Anthropic
14+
from dotenv import load_dotenv
15+
16+
load_dotenv() # load environment variables from .env
17+
18+
19+
class MCPClient:
20+
def __init__(self):
21+
# Initialize session and client objects
22+
self.session: Optional[ClientSession] = None
23+
self.exit_stack = AsyncExitStack()
24+
self.anthropic = Anthropic()
25+
26+
# methods will go here
27+
async def connect_to_server(self, server_script_path: str):
28+
"""Connect to an MCP server
29+
30+
Args:
31+
server_script_path: Path to the server script (.py or .js)
32+
"""
33+
print("Starting Server from within client")
34+
is_python = server_script_path.endswith(".py")
35+
is_js = server_script_path.endswith(".js")
36+
if not (is_python or is_js):
37+
raise ValueError("Server script must be a .py or .js file")
38+
39+
command = "python" if is_python else "node"
40+
server_params = StdioServerParameters(
41+
command=command, args=[server_script_path], env=None
42+
)
43+
print("This is where the server is running")
44+
stdio_transport = await self.exit_stack.enter_async_context(
45+
stdio_client(server_params)
46+
)
47+
# print("got the transport...", stdio_transport)
48+
self.stdio, self.write = stdio_transport
49+
self.session = await self.exit_stack.enter_async_context(
50+
ClientSession(self.stdio, self.write)
51+
)
52+
53+
await self.session.initialize()
54+
print("session initialized")
55+
56+
# List available tools
57+
response = await self.session.list_tools()
58+
tools = response.tools
59+
# Need to get tools output if the server is up
60+
print("\nConnected to server with tools:", [tool.name for tool in tools])
61+
62+
# below code is used initially for testing the read_resource method
63+
resource_test = await self.session.read_resource("subreddit://info")
64+
print("Testing Resource in Client side:", resource_test)
65+
66+
# listing available prompts
67+
response = await self.session.list_prompts()
68+
prompts = response.prompts
69+
print("\nAvailable prompts:", [prompt.name for prompt in prompts])
70+
71+
async def process_query(self, query: str) -> str:
72+
"""Process a query using Claude and available tools"""
73+
# get the tools
74+
response = await self.session.list_tools()
75+
76+
messages = [{"role": "user", "content": query}]
77+
available_tools = [
78+
{
79+
"name": tool.name,
80+
"description": tool.description,
81+
"input_schema": tool.inputSchema,
82+
}
83+
for tool in response.tools
84+
]
85+
86+
# Initial Claude API call
87+
response = self.anthropic.messages.create(
88+
model="claude-3-5-haiku-20241022",
89+
max_tokens=1000,
90+
messages=messages,
91+
tools=available_tools,
92+
)
93+
94+
# Process response and handle tool calls
95+
tool_results = []
96+
final_text = []
97+
98+
for content in response.content:
99+
if content.type == "text":
100+
final_text.append(content.text)
101+
elif content.type == "tool_use":
102+
tool_name = content.name
103+
tool_args = content.input
104+
105+
# Execute tool call
106+
result = await self.session.call_tool(tool_name, tool_args)
107+
print(f"Tool result call: {result.content}")
108+
tool_results.append({"call": tool_name, "result": result})
109+
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
110+
111+
# Continue conversation with tool results
112+
if hasattr(content, "text") and content.text:
113+
messages.append({"role": "assistant", "content": content.text})
114+
messages.append({"role": "user", "content": result.content})
115+
116+
# Get next response from Claude
117+
response = self.anthropic.messages.create(
118+
model="claude-3-5-haiku-20241022",
119+
max_tokens=1000,
120+
messages=messages,
121+
)
122+
123+
final_text.append(response.content[0].text)
124+
125+
return "\n".join(final_text)
126+
127+
async def chat_loop(self):
128+
"""Run an interactive chat loop"""
129+
print("Type your market research queries or 'quit' to exit.")
130+
131+
while True:
132+
try:
133+
query = input("\nQuery: ").strip()
134+
135+
if query.lower() == "quit":
136+
break
137+
138+
response = await self.process_query(query)
139+
print("\n" + response)
140+
141+
except Exception as e:
142+
print(f"\nError: {str(e)}")
143+
144+
async def cleanup(self):
145+
"""Clean up resources"""
146+
await self.exit_stack.aclose()
147+
148+
149+
async def main():
150+
if len(sys.argv) < 2:
151+
print("Usage: uv run client.py server.py")
152+
sys.exit(1)
153+
154+
client = MCPClient()
155+
try:
156+
await client.connect_to_server(sys.argv[1])
157+
await client.chat_loop()
158+
finally:
159+
await client.cleanup()
160+
161+
162+
if __name__ == "__main__":
163+
import sys
164+
165+
asyncio.run(main())

fw_ex/praw_spiked/mcp-reddit-server/server.py

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
1+
# /// script
2+
# requires-python = ">=3.11"
3+
# dependencies = [
4+
# "mcp",
5+
# "praw",
6+
# "python-dotenv",
7+
# "anthropic",
8+
# ]
9+
# ///
10+
from collections.abc import Iterable
111
from mcp.server.fastmcp import FastMCP, Context
212
from mcp.server.fastmcp.prompts import base
13+
from mcp.server.lowlevel.helper_types import ReadResourceContents
14+
from typing import List
315
import praw
416
from dotenv import load_dotenv
517
import os
@@ -64,30 +76,45 @@ def subreddit_info_static() -> str:
6476

6577

6678
# @mcp.tool()
67-
# async def get_subreddit_info(query: str, ctx: Context) -> str:
68-
# """Answer the user query by accessing the subreddit_info from
69-
# get_subreddit resource. Use the reply_with_context prompt"""
70-
71-
# info = await ctx.read_resource("subreddit_info")
72-
# # making the prompt
73-
# prompt = mcp.get_prompt(
74-
# "reply_with_context", arguments={"context": info, "query": query}
75-
# )
76-
# # Building messages
77-
# messages = [
78-
# {
79-
# "role": "user",
80-
# "content": prompt,
81-
# }
82-
# ]
83-
# # Calling the model
84-
# response = anthropic.messages.create(
85-
# model="claude-3-5-haiku-20241022",
86-
# max_tokens=500,
87-
# messages=messages,
88-
# )
89-
# # returning the reply.
90-
# return response.content[0].text
79+
# async def get_subreddit_info(query: str) -> Iterable[ReadResourceContents]:
80+
# async def get_subreddit_info(query: str) -> str:
81+
# """Answer the user query by accessing the subreddit_info from
82+
# get_subreddit resource. Use the reply_with_context prompt"""
83+
84+
# data = await mcp.read_resource("subreddit://info")
85+
# # making the prompt
86+
# prompt = mcp.get_prompt(
87+
# "reply_with_context",
88+
# arguments={"context": data.contents[0].text, "query": query},
89+
# )
90+
# returning the reply.
91+
# return prompt.messages[0].content.text
92+
# return data
93+
# return data[0].content
94+
95+
96+
@mcp.tool()
97+
# async def get_subreddit_info(query: str) -> Iterable[ReadResourceContents]:
98+
async def get_subreddit_info(query: str) -> str:
99+
"""Used for answering the user query relating to subreddit informations"""
100+
101+
data = await mcp.read_resource("subreddit://info")
102+
# making the prompt
103+
prompt = await mcp.get_prompt(
104+
"reply_with_context",
105+
arguments={"context": data[0].content, "query": query},
106+
)
107+
# returning the reply.
108+
# return prompt.model_dump_json()
109+
return prompt.messages[0].content.text
110+
111+
112+
# @mcp.tool()
113+
# async def get_resources() -> str:
114+
# """Returns the list of resources available with you"""
115+
# resource_list = await mcp.list_resources()
116+
# res_list_str = ",".join([res.name for res in resource_list])
117+
# return f"Available resources with you are: {res_list_str}"
91118

92119

93120
@mcp.tool()
@@ -112,4 +139,5 @@ def reply_comment(comment_text: str) -> str:
112139

113140

114141
if __name__ == "__main__":
142+
print("Starting MCP Server")
115143
mcp.run()

fw_ex/spike_mcp/mcp-echo-server/main.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,12 @@ def debug_error(error: str) -> list[base.Message]:
6060

6161

6262
@mcp.tool()
63-
async def long_task(files: list[str], ctx: Context) -> str:
63+
async def long_task(files: str, ctx: Context) -> str:
6464
"""Process multiple files with progress tracking"""
6565
file_data = ""
6666
for i, file in enumerate(files):
6767
await ctx.info(f"Processing {file}")
6868
await ctx.report_progress(i, len(files))
69-
ctx.
7069
data, mime_type = await ctx.read_resource("local://main")
7170
await ctx.info(f"File type: {mime_type}")
7271
await ctx.info(f"{data.content}")

0 commit comments

Comments
 (0)