Skip to content

Commit 69e25ef

Browse files
committed
feat: add tests for MCP integration
1 parent 545b1c4 commit 69e25ef

8 files changed

Lines changed: 2235 additions & 0 deletions

File tree

test-mcp/main.py

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example MCP server using mcp.fastmcp with Sentry integration.
4+
5+
This demonstrates:
6+
- Tools: Functions that can be called by the client
7+
- Resources: Data that can be accessed by the client
8+
- Prompts: Pre-defined prompt templates
9+
"""
10+
import os
11+
import sys
12+
import sentry_sdk
13+
from sentry_sdk.integrations.mcp import MCPIntegration
14+
15+
from mcp.server.fastmcp import FastMCP, Context
16+
from pydantic import BaseModel, Field
17+
from typing import List
18+
19+
20+
sentry_sdk.init(
21+
dsn=os.environ.get("SENTRY_DSN"),
22+
environment=os.environ.get("ENV", "test-sync"),
23+
traces_sample_rate=1.0,
24+
send_default_pii=True,
25+
debug=True,
26+
integrations=[MCPIntegration()],
27+
)
28+
29+
# Create the MCP server
30+
mcp = FastMCP("Example MCP Server")
31+
32+
33+
# Define Pydantic models for structured output
34+
class TextStatistics(BaseModel):
35+
"""Statistics about a text string."""
36+
character_count: int = Field(description="Total number of characters")
37+
word_count: int = Field(description="Total number of words")
38+
line_count: int = Field(description="Total number of lines")
39+
sentence_count: int = Field(description="Approximate number of sentences")
40+
average_word_length: float = Field(description="Average length of words")
41+
longest_word: str = Field(description="The longest word in the text")
42+
shortest_word: str = Field(description="The shortest word in the text")
43+
44+
45+
class UserInfo(BaseModel):
46+
"""User information structure."""
47+
id: int = Field(description="User ID")
48+
name: str = Field(description="User name")
49+
email: str = Field(description="User email")
50+
role: str = Field(description="User role")
51+
active: bool = Field(description="Whether the user is active")
52+
53+
54+
class UserList(BaseModel):
55+
"""List of users with metadata."""
56+
total: int = Field(description="Total number of users")
57+
users: List[UserInfo] = Field(description="List of user objects")
58+
59+
60+
# Define a tool - these are functions the client can call
61+
@mcp.tool()
62+
async def calculate_sum(a: int, b: int, ctx: Context) -> int:
63+
"""Add two numbers together."""
64+
return a + b
65+
66+
67+
@mcp.tool()
68+
async def calculate_sum_no_context(a: int, b: int) -> int:
69+
"""Add two numbers together."""
70+
return a + b
71+
72+
73+
@mcp.tool()
74+
def calculate_product(a: int, b: int) -> int:
75+
"""Multiply two numbers together."""
76+
return a * b
77+
78+
79+
@mcp.tool()
80+
def greet_user(name: str) -> str:
81+
"""Generate a personalized greeting."""
82+
return f"Hello, {name}! Welcome to the MCP server."
83+
84+
85+
@mcp.tool()
86+
def trigger_error() -> str:
87+
"""Trigger an error to test Sentry integration."""
88+
raise ValueError("This is a test error to verify Sentry is working!")
89+
90+
91+
@mcp.tool()
92+
def analyze_text(text: str) -> TextStatistics:
93+
"""Analyze text and return structured statistics.
94+
95+
This demonstrates structured output using a Pydantic model.
96+
"""
97+
import re
98+
99+
# Calculate character count
100+
character_count = len(text)
101+
102+
# Calculate line count
103+
lines = text.split('\n')
104+
line_count = len(lines)
105+
106+
# Calculate word count and word-related statistics
107+
words = text.split()
108+
word_count = len(words)
109+
110+
# Filter out words that are only punctuation
111+
actual_words = [word.strip('.,!?;:()[]{}"\'-') for word in words]
112+
actual_words = [word for word in actual_words if word]
113+
114+
if actual_words:
115+
average_word_length = sum(len(word) for word in actual_words) / len(actual_words)
116+
longest_word = max(actual_words, key=len)
117+
shortest_word = min(actual_words, key=len)
118+
else:
119+
average_word_length = 0.0
120+
longest_word = ""
121+
shortest_word = ""
122+
123+
# Calculate sentence count (approximate)
124+
sentence_endings = re.findall(r'[.!?]+', text)
125+
sentence_count = len(sentence_endings) if sentence_endings else 1
126+
127+
return TextStatistics(
128+
character_count=character_count,
129+
word_count=word_count,
130+
line_count=line_count,
131+
sentence_count=sentence_count,
132+
average_word_length=round(average_word_length, 2),
133+
longest_word=longest_word,
134+
shortest_word=shortest_word,
135+
)
136+
137+
138+
@mcp.tool()
139+
def get_user_list(include_inactive: bool = False) -> UserList:
140+
"""Get a list of users with structured information.
141+
142+
This demonstrates structured output with nested Pydantic models.
143+
"""
144+
all_users = [
145+
UserInfo(id=1, name="Alice Smith", email="alice@example.com", role="admin", active=True),
146+
UserInfo(id=2, name="Bob Johnson", email="bob@example.com", role="user", active=True),
147+
UserInfo(id=3, name="Charlie Brown", email="charlie@example.com", role="user", active=True),
148+
UserInfo(id=4, name="Diana Prince", email="diana@example.com", role="moderator", active=False),
149+
UserInfo(id=5, name="Eve Davis", email="eve@example.com", role="user", active=False),
150+
]
151+
152+
if include_inactive:
153+
filtered_users = all_users
154+
else:
155+
filtered_users = [user for user in all_users if user.active]
156+
157+
return UserList(
158+
total=len(filtered_users),
159+
users=filtered_users,
160+
)
161+
162+
163+
# Define resources - these provide access to data
164+
@mcp.resource("config://settings")
165+
def get_settings() -> str:
166+
"""Get server configuration settings."""
167+
return """
168+
Server Configuration:
169+
- Version: 1.0.0
170+
- Environment: Development
171+
- Max Connections: 100
172+
"""
173+
174+
175+
@mcp.resource("data://users")
176+
def get_users() -> str:
177+
"""Get a list of sample users."""
178+
return """
179+
Users:
180+
1. Alice (admin@example.com)
181+
2. Bob (bob@example.com)
182+
3. Charlie (charlie@example.com)
183+
"""
184+
185+
186+
# Define prompts - these are reusable prompt templates
187+
@mcp.prompt()
188+
def code_review_prompt(language: str = "python") -> str:
189+
"""Generate a code review prompt for a specific language."""
190+
return f"""You are an expert {language} code reviewer. Please review the following code and provide:
191+
192+
1. Code quality assessment
193+
2. Potential bugs or issues
194+
3. Performance improvements
195+
4. Best practices recommendations
196+
5. Security considerations
197+
198+
Be specific and constructive in your feedback."""
199+
200+
201+
@mcp.prompt()
202+
def debug_assistant_prompt() -> str:
203+
"""Generate a debugging assistant prompt."""
204+
return """You are a debugging assistant. Help the user:
205+
206+
1. Understand the error message
207+
2. Identify the root cause
208+
3. Suggest potential fixes
209+
4. Provide prevention strategies
210+
211+
Ask clarifying questions if needed."""
212+
213+
214+
215+
# mcp.run()
216+
# def main():
217+
# # Run the MCP server
218+
# mcp.run()
219+
220+
# if __name__ == "__main__":
221+
# main()

0 commit comments

Comments
 (0)