discourse/plugins/discourse-ai/app/serializers/localized_ai_agent_serializer.rb
Sam b2d73b346d
FEATURE: Add MCP server integration to AI agents (#38706)
Introduce support for Model Context Protocol (MCP) servers in the
discourse-ai plugin, allowing AI agents to connect to external tool
servers via the MCP standard.

Key additions:
- AiMcpServer model with CRUD admin UI, health tracking, and
  tool caching (hourly refresh via scheduled job)
- MCP client (Streamable HTTP transport) with session management
  and tool invocation
- Full OAuth 2.1 flow support (discovery, dynamic registration,
  authorization code grant, token refresh, and disconnect)
- MCP tool type for AI agents that proxies tool calls to remote
  MCP servers at runtime
- Agent editor updated to show combined tool/token counts from
  both local tools and MCP servers
- Agent import/export includes MCP server associations
- Admin secrets UI updated to surface MCP server usage
- Comprehensive specs for models, controllers, client, tool
  registry, and OAuth flow
2026-03-25 17:32:27 +11:00

111 lines
3.1 KiB
Ruby
Vendored

# frozen_string_literal: true
class LocalizedAiAgentSerializer < ApplicationSerializer
root "ai_agent"
attributes :id,
:name,
:description,
:enabled,
:system,
:priority,
:tools,
:system_prompt,
:allowed_group_ids,
:temperature,
:top_p,
:default_llm_id,
:user_id,
:max_context_posts,
:vision_enabled,
:vision_max_pixels,
:rag_chunk_tokens,
:rag_chunk_overlap_tokens,
:rag_conversation_chunks,
:rag_llm_model_id,
:show_thinking,
:forced_tool_count,
:allow_chat_channel_mentions,
:allow_chat_direct_messages,
:allow_topic_mentions,
:allow_personal_messages,
:force_default_llm,
:execution_mode,
:max_turn_tokens,
:compression_threshold,
:require_approval,
:mcp_server_ids,
:mcp_server_tool_names,
:mcp_servers,
:response_format,
:examples,
:features
has_one :user, serializer: BasicUserSerializer, embed: :object
has_many :rag_uploads, serializer: UploadSerializer, embed: :object
has_one :default_llm, serializer: BasicLlmModelSerializer, embed: :object
def rag_uploads
object.uploads
end
def name
object.class_instance.name
end
def description
object.class_instance.description
end
def default_llm
LlmModel.find_by(id: object.default_llm_id)
end
def features
object.features.map do |feature|
{ id: feature.module_id, module_name: feature.module_name, name: feature.name }
end
end
def mcp_server_ids
enabled_mcp_server_assignments.filter_map(&:ai_mcp_server_id)
end
def mcp_server_tool_names
enabled_mcp_server_assignments.each_with_object({}) do |assignment, hash|
next if assignment.all_tools_enabled?
hash[assignment.ai_mcp_server_id.to_s] = assignment.selected_tool_names
end
end
def mcp_servers
sorted_enabled_mcp_server_assignments.map do |assignment|
server = assignment.ai_mcp_server
{
id: server.id,
name: server.name,
tool_count: server.tool_count,
token_count: server.token_count,
selected_tool_names: assignment.selected_tool_names,
selected_tool_count: assignment.tool_count,
selected_token_count: assignment.token_count,
all_tools_enabled: assignment.all_tools_enabled?,
last_health_status: server.last_health_status,
last_checked_at: server.last_checked_at,
}
end
end
private
def enabled_mcp_server_assignments
@enabled_mcp_server_assignments ||=
object.ai_agent_mcp_servers.select { |assignment| assignment.ai_mcp_server&.enabled? }
end
def sorted_enabled_mcp_server_assignments
@sorted_enabled_mcp_server_assignments ||=
enabled_mcp_server_assignments.sort_by { |assignment| assignment.ai_mcp_server.name.downcase }
end
end