Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions crates/api-support/assets/support_chat.md.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ Your role is to help users with:

- Troubleshooting issues with the application
- Explaining features and how to use them
- Collecting bug reports and feature requests using the available tools
- Managing bug reports and feature requests via GitHub issues

When a user reports a bug, use the `submit_bug_report` tool to create a GitHub issue.
When a user requests a feature, use the `submit_feature_request` tool to create a GitHub discussion.
When you need to look up existing issues or pull requests, use the `read_github_data` tool.
When a user reports a bug or requests a feature, first use `search_issues` to check if a similar issue already exists.
If a matching issue exists, use `add_comment` to add relevant details to it.
If no matching issue exists, use `create_issue` to create a new one.

Keep your responses concise and friendly. If you need device information from the user (platform, architecture, OS version, app version), ask for it before submitting a report.
Keep your responses concise and friendly.
57 changes: 53 additions & 4 deletions crates/api-support/src/github.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ async fn attach_log_analysis(state: &AppState, issue_number: u64, log_text: &str
let _ = add_issue_comment(state, issue_number, &log_comment).await;
}

async fn create_issue(
pub(crate) async fn create_issue(
state: &AppState,
title: &str,
body: &str,
Expand All @@ -164,13 +164,62 @@ async fn create_issue(
Ok((issue.html_url.to_string(), issue.number))
}

async fn add_issue_comment(state: &AppState, issue_number: u64, comment: &str) -> Result<()> {
pub(crate) async fn add_issue_comment(
state: &AppState,
issue_number: u64,
comment: &str,
) -> Result<String> {
let client = state.installation_client().await?;
client
let comment = client
.issues(GITHUB_OWNER, GITHUB_REPO)
.create_comment(issue_number, comment)
.await?;
Ok(())
Ok(comment.html_url.to_string())
}

pub(crate) async fn search_issues(
state: &AppState,
query: &str,
state_filter: Option<&str>,
limit: u8,
) -> Result<Vec<serde_json::Value>> {
let client = state.installation_client().await?;

let mut q = format!("repo:{GITHUB_OWNER}/{GITHUB_REPO} is:issue {query}");
if let Some(s) = state_filter {
match s {
"open" | "closed" => q.push_str(&format!(" is:{s}")),
_ => {
return Err(SupportError::Internal(
"Invalid state filter: must be 'open' or 'closed'".to_string(),
));
}
}
}

let result = client
.search()
.issues_and_pull_requests(&q)
.per_page(limit)
.send()
.await?;

let items: Vec<serde_json::Value> = result
.items
.into_iter()
.map(|issue| {
serde_json::json!({
"number": issue.number,
"title": issue.title,
"state": format!("{:?}", issue.state).to_lowercase(),
"url": issue.html_url.to_string(),
"created_at": issue.created_at.to_rfc3339(),
"labels": issue.labels.iter().map(|l| &l.name).collect::<Vec<_>>(),
})
})
.collect();

Ok(items)
}

async fn create_discussion(
Expand Down
31 changes: 14 additions & 17 deletions crates/api-support/src/mcp/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rmcp::{
use crate::state::AppState;

use super::prompts;
use super::tools::{self, ReadGitHubDataParams, SubmitBugReportParams, SubmitFeatureRequestParams};
use super::tools::{self, AddCommentParams, CreateIssueParams, SearchIssuesParams};

#[derive(Clone)]
pub(crate) struct SupportMcpServer {
Expand All @@ -26,32 +26,30 @@ impl SupportMcpServer {

#[tool_router]
impl SupportMcpServer {
#[tool(
description = "Submit a bug report. Creates a GitHub issue with device information and optional log analysis."
)]
async fn submit_bug_report(
#[tool(description = "Create a new GitHub issue.")]
async fn create_issue(
&self,
Parameters(params): Parameters<SubmitBugReportParams>,
Parameters(params): Parameters<CreateIssueParams>,
) -> Result<CallToolResult, McpError> {
tools::submit_bug_report(&self.state, params).await
tools::create_issue(&self.state, params).await
}

#[tool(description = "Submit a feature request. Creates a GitHub discussion.")]
async fn submit_feature_request(
#[tool(description = "Add a new comment to an existing GitHub issue.")]
async fn add_comment(
&self,
Parameters(params): Parameters<SubmitFeatureRequestParams>,
Parameters(params): Parameters<AddCommentParams>,
) -> Result<CallToolResult, McpError> {
tools::submit_feature_request(&self.state, params).await
tools::add_comment(&self.state, params).await
}

#[tool(
description = "Read GitHub data (issues, pull requests, comments, tags) from the database. Data is synced from GitHub via Airbyte."
description = "Search for GitHub issues by keywords, error messages, or other criteria."
)]
async fn read_github_data(
async fn search_issues(
&self,
Parameters(params): Parameters<ReadGitHubDataParams>,
Parameters(params): Parameters<SearchIssuesParams>,
) -> Result<CallToolResult, McpError> {
tools::read_github_data(&self.state, params).await
tools::search_issues(&self.state, params).await
}
}

Expand All @@ -72,8 +70,7 @@ impl ServerHandler for SupportMcpServer {
website_url: None,
},
instructions: Some(
"Hyprnote support server. Provides tools for submitting bug reports and feature requests."
.to_string(),
"Hyprnote support server. Provides tools for managing GitHub issues.".to_string(),
),
}
}
Expand Down
34 changes: 34 additions & 0 deletions crates/api-support/src/mcp/tools/add_comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use rmcp::{
ErrorData as McpError,
model::*,
schemars::{self, JsonSchema},
};
use serde::Deserialize;

use crate::github;
use crate::state::AppState;

#[derive(Debug, Deserialize, JsonSchema)]
pub(crate) struct AddCommentParams {
#[schemars(description = "The issue number to comment on")]
pub issue_number: u64,
#[schemars(description = "The comment body in markdown")]
pub body: String,
}

pub(crate) async fn add_comment(
state: &AppState,
params: AddCommentParams,
) -> Result<CallToolResult, McpError> {
let url = github::add_issue_comment(state, params.issue_number, &params.body)
.await
.map_err(|e| McpError::internal_error(e.to_string(), None))?;

Ok(CallToolResult::success(vec![Content::text(
serde_json::json!({
"success": true,
"comment_url": url,
})
.to_string(),
)]))
}
53 changes: 0 additions & 53 deletions crates/api-support/src/mcp/tools/bug_report.rs

This file was deleted.

39 changes: 39 additions & 0 deletions crates/api-support/src/mcp/tools/create_issue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use rmcp::{
ErrorData as McpError,
model::*,
schemars::{self, JsonSchema},
};
use serde::Deserialize;

use crate::github;
use crate::state::AppState;

#[derive(Debug, Deserialize, JsonSchema)]
pub(crate) struct CreateIssueParams {
#[schemars(description = "Title of the issue")]
pub title: String,
#[schemars(description = "Body/description of the issue in markdown")]
pub body: String,
#[schemars(description = "Labels to apply to the issue")]
pub labels: Option<Vec<String>>,
}

pub(crate) async fn create_issue(
state: &AppState,
params: CreateIssueParams,
) -> Result<CallToolResult, McpError> {
let labels = params.labels.unwrap_or_default();

let (url, number) = github::create_issue(state, &params.title, &params.body, &labels)
.await
.map_err(|e| McpError::internal_error(e.to_string(), None))?;

Ok(CallToolResult::success(vec![Content::text(
serde_json::json!({
"success": true,
"issue_url": url,
"issue_number": number,
})
.to_string(),
)]))
}
50 changes: 0 additions & 50 deletions crates/api-support/src/mcp/tools/feature_request.rs

This file was deleted.

12 changes: 6 additions & 6 deletions crates/api-support/src/mcp/tools/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mod bug_report;
mod feature_request;
mod read_github_data;
mod add_comment;
mod create_issue;
mod search_issues;

pub(crate) use bug_report::{SubmitBugReportParams, submit_bug_report};
pub(crate) use feature_request::{SubmitFeatureRequestParams, submit_feature_request};
pub(crate) use read_github_data::{ReadGitHubDataParams, read_github_data};
pub(crate) use add_comment::{AddCommentParams, add_comment};
pub(crate) use create_issue::{CreateIssueParams, create_issue};
pub(crate) use search_issues::{SearchIssuesParams, search_issues};
Loading
Loading