Skip to content

Commit a750eac

Browse files
Merge pull request #18 from insightbuilder/reddit-mcp-trend
Reddit mcp trend server
2 parents ebd1d2a + 2b43eff commit a750eac

22 files changed

Lines changed: 2478 additions & 4 deletions

File tree

docs/mcp_reddit_server.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# MCP Server to Connect with Reddit
2+
3+
MCP servers provide a robust access to the backend
4+
APIs, file systems and even the display of a
5+
computer.
6+
7+
We are implementing the MCP Server to connect with
8+
Reddit API and call it aptly mcp-reddit-server.
9+
This is part of the vabired project.
10+
11+
## Project Details:
12+
13+
- Project Path:
14+
../fw_ex/praw_spiked/mcp-reddit-server
15+
16+
### Project setup commands:
17+
18+
```bash
19+
cd ../fw_ex/praw_spiked
20+
uv init mcp-reddit-server
21+
22+
cd mcp-reddit-server
23+
uv add mcp "mcp[cli]" httpx praw pythod-dotenv httpx anthropic
24+
25+
mv main.py server.py
26+
27+
touch client.py
28+
29+
# Above two steps are not required if you are cloning this repo
30+
31+
uv run client.py server.py
32+
33+
After the client start, you will be prompted for the query.
34+
35+
Query: You are reddit analysing agent. Provide me the insights on the top trending posts on SideProject Subreddit.
36+
```
37+
38+
When you need use mcp inspector to debug the code,
39+
use the command below. Ensure you have
40+
[npx](https://docs.npmjs.com/cli/v8/commands/npx)
41+
and [node](https://nodejs.org/en/download)
42+
installed
43+
44+
````bash
45+
npx @modelcontextprotocol/inspector uv run server.py
46+
```
47+
48+
### Project Description:
49+
50+
The server.py contains the tools to connect with
51+
reddit API, and the client.py contains the code to
52+
connect with LLMs and user.
53+
54+
The functions used in the server.py is taken from
55+
the ../fw_ex/praw_spiked/vabired_app02.py. The MCP
56+
server and client are a different interface to the
57+
way we interact with the computers. So instead of
58+
using REST API servers, we will use plain english.
59+
60+
### Project References
61+
62+
- Server code referred from :
63+
[MCP Installation](https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#installation)
64+
65+
- Client code referred from:
66+
[Introducing Clients](https://modelcontextprotocol.io/quickstart/client)
67+
68+
- Praw Code referred from:
69+
[Vabired code](../docs/vabired_docs.md)
70+
````
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Python-generated files
2+
__pycache__/
3+
*.py[oc]
4+
build/
5+
dist/
6+
wheels/
7+
*.egg-info
8+
9+
# Virtual environments
10+
.venv
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.11

fw_ex/praw_spiked/mcp-reddit-server/README.md

Whitespace-only changes.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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+
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)
74+
print("\nAvailable prompts:", [prompt.name for prompt in prompts])
75+
76+
async def process_query(self, query: str) -> str:
77+
"""Process a query using Claude and available tools"""
78+
# get the tools
79+
response = await self.session.list_tools()
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)
89+
# build it into the message list
90+
messages = [{"role": "user", "content": query}]
91+
available_tools = [
92+
{
93+
"name": tool.name,
94+
"description": tool.description,
95+
"input_schema": tool.inputSchema,
96+
}
97+
for tool in response.tools
98+
]
99+
100+
# Initial Claude API call
101+
response = self.anthropic.messages.create(
102+
model="claude-3-5-haiku-20241022",
103+
max_tokens=1000,
104+
messages=messages,
105+
tools=available_tools,
106+
)
107+
108+
# Process response and handle tool calls
109+
tool_results = []
110+
final_text = []
111+
112+
for content in response.content:
113+
if content.type == "text":
114+
final_text.append(content.text)
115+
elif content.type == "tool_use":
116+
tool_name = content.name
117+
tool_args = content.input
118+
119+
# Execute tool call
120+
result = await self.session.call_tool(tool_name, tool_args)
121+
tool_results.append({"call": tool_name, "result": result})
122+
final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")
123+
print(f"Toolcall Result: {result.content}")
124+
# Continue conversation with tool results
125+
if hasattr(content, "text") and content.text:
126+
messages.append({"role": "assistant", "content": content.text})
127+
messages.append({"role": "user", "content": result.content})
128+
129+
# Get next response from Claude
130+
response = self.anthropic.messages.create(
131+
model="claude-3-5-haiku-20241022",
132+
max_tokens=1000,
133+
messages=messages,
134+
)
135+
136+
final_text.append(response.content[0].text)
137+
138+
return "\n".join(final_text)
139+
140+
async def chat_loop(self):
141+
"""Run an interactive chat loop"""
142+
print("Type your market research queries or 'quit' to exit.")
143+
144+
while True:
145+
try:
146+
query = input("\nQuery: ").strip()
147+
148+
if query.lower() == "quit":
149+
break
150+
151+
response = await self.process_query(query)
152+
print("\n" + response)
153+
154+
except Exception as e:
155+
print(f"\nError: {str(e)}")
156+
157+
async def cleanup(self):
158+
"""Clean up resources"""
159+
await self.exit_stack.aclose()
160+
161+
162+
async def main():
163+
if len(sys.argv) < 2:
164+
print("Usage: uv run client.py server.py")
165+
sys.exit(1)
166+
167+
client = MCPClient()
168+
try:
169+
await client.connect_to_server(sys.argv[1])
170+
await client.chat_loop()
171+
finally:
172+
await client.cleanup()
173+
174+
175+
if __name__ == "__main__":
176+
import sys
177+
178+
asyncio.run(main())

0 commit comments

Comments
 (0)