EmbeddingSink queues text from each batch on its own bounded queue and
upserts vectors into a new {prefix}embeddings table (pgvector on Postgres,
JSON elsewhere). mw.similar() and mw.similar_feedback() search it.
submit_feedback records a feedback.submitted event; record_llm_call keeps
prompt and completion only with capture_llm_text=True.
123 lines
4.7 KiB
Python
123 lines
4.7 KiB
Python
"""Submission mixin for feedback collection."""
|
|
|
|
from datetime import UTC, datetime
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from ..insights import record_feedback_submission
|
|
from ..models import SubmitFeedbackRequest, create_feedback_from_request
|
|
from .base import (
|
|
BaseFeedbackMixin,
|
|
FeedbackDatabase,
|
|
FeedbackInsights,
|
|
logger,
|
|
mcp_tool,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from ..instrumentation import InstrumentationMiddleware
|
|
|
|
|
|
class SubmissionMixin(BaseFeedbackMixin):
|
|
"""Mixin for feedback submission functionality.
|
|
|
|
Pass an InstrumentationMiddleware to link each submission to the tool
|
|
calls that led up to it and record a ``feedback.submitted`` event for it.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
database: FeedbackDatabase,
|
|
insights: FeedbackInsights | None = None,
|
|
instrumentation: "InstrumentationMiddleware | None" = None,
|
|
):
|
|
super().__init__(database, insights)
|
|
self.instrumentation = instrumentation
|
|
|
|
@mcp_tool(
|
|
description="Submit feedback about your MCP experience - Use this tool when you encounter issues, have suggestions for improvement, or want to report positive experiences. AI models should proactively use this to help improve future MCP sessions. PRIVACY: Only include information you've already shared via previous MCP calls. Ask human permission before including any new sensitive data."
|
|
)
|
|
async def submit_feedback(self, request: SubmitFeedbackRequest) -> dict[str, Any]:
|
|
"""Submit feedback about the MCP server, tools, or interaction experience.
|
|
|
|
AI models should use this tool to:
|
|
- Report when tools don't work as expected (type='bug')
|
|
- Suggest improvements to tool functionality (type='improvement')
|
|
- Request new capabilities that would be helpful (type='feature')
|
|
- Ask questions about unclear tool behavior (type='question')
|
|
|
|
PRIVACY GUIDELINES:
|
|
- Only include information already shared in this MCP session
|
|
- Avoid including sensitive data not previously disclosed
|
|
- When in doubt, ask the human for permission first
|
|
- Focus on the specific technical issue, not broader context
|
|
|
|
This helps improve the MCP server and tools for future sessions.
|
|
|
|
Args:
|
|
request: Feedback submission request with type, title, description, submitter.
|
|
|
|
Returns:
|
|
Dictionary with submission result including feedback_id if successful.
|
|
"""
|
|
start_time = datetime.now(UTC)
|
|
|
|
try:
|
|
# Create feedback from request
|
|
feedback = create_feedback_from_request(request)
|
|
|
|
# Save to database
|
|
async with self.database.session() as session:
|
|
session.add(feedback)
|
|
session.commit()
|
|
session.refresh(feedback)
|
|
|
|
feedback_id = str(feedback.id)
|
|
|
|
# Record analytics (without PII)
|
|
if self.insights.enabled:
|
|
record_feedback_submission(
|
|
self.insights,
|
|
feedback_type=request.type,
|
|
has_contact=bool(request.contact_info),
|
|
title_length=len(request.title),
|
|
description_length=len(request.description),
|
|
source="mcp_tool",
|
|
)
|
|
|
|
linked_calls = 0
|
|
if self.instrumentation is not None:
|
|
# Feeds an EmbeddingSink, so similar reports can be found.
|
|
self.instrumentation.record_event(
|
|
"feedback.submitted",
|
|
key=feedback_id,
|
|
attrs={
|
|
"type": request.type,
|
|
"title": request.title,
|
|
"description": request.description,
|
|
"submitter": request.submitter,
|
|
},
|
|
)
|
|
links = await self.instrumentation.capture_recent_calls()
|
|
if await self.instrumentation.link_feedback(feedback_id, links):
|
|
linked_calls = len(links)
|
|
|
|
self._record_tool_usage("submit_feedback", start_time, True)
|
|
logger.info(f"Feedback submitted successfully: {feedback_id}")
|
|
|
|
response = {
|
|
"success": True,
|
|
"feedback_id": feedback_id,
|
|
"message": "Feedback submitted successfully",
|
|
}
|
|
if self.instrumentation is not None:
|
|
response["linked_calls"] = linked_calls
|
|
return response
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to submit feedback: {e}")
|
|
self._record_tool_usage(
|
|
"submit_feedback", start_time, False, type(e).__name__
|
|
)
|
|
|
|
return {"success": False, "error": str(e)}
|