list discord command

This commit is contained in:
mruwnik 2025-11-03 19:09:53 +00:00
parent 8893018af1
commit b568222e88
4 changed files with 488 additions and 233 deletions

View File

@ -51,21 +51,40 @@ class MessageProcessor:
), ),
) )
def as_xml(self) -> str: @property
return ( def entity_type(self) -> str:
textwrap.dedent(""" return self.__class__.__tablename__[8:-1] # type: ignore
<{type}>
<name>{name}</name> def to_xml(self, fields: list[str]) -> str:
<summary>{summary}</summary> def indent(key: str, text: str) -> str:
</{type}> res = textwrap.dedent("""
""") <{key}>
.format( {text}
type=self.__class__.__tablename__[8:], # type: ignore </{key}>
name=getattr(self, "name", None) or getattr(self, "username", None), """).format(key=key, text=textwrap.indent(text, " "))
summary=self.summary, return res.strip()
)
.strip() vals = []
) if "name" in fields:
vals.append(indent("name", self.name))
if "system_prompt" in fields:
vals.append(indent("system_prompt", self.system_prompt or ""))
if "summary" in fields:
vals.append(indent("summary", self.summary or ""))
if "mcp_servers" in fields:
servers = [s.as_xml() for s in self.mcp_servers]
vals.append(indent("mcp_servers", "\n".join(servers)))
return indent(self.entity_type, "\n".join(vals)) # type: ignore
def xml_prompt(self) -> str:
return self.to_xml(["name", "system_prompt"]) if self.system_prompt else ""
def xml_summary(self) -> str:
return self.to_xml(["name", "summary"])
def xml_mcp_servers(self) -> str:
return self.to_xml(["mcp_servers"])
class DiscordServer(Base, MessageProcessor): class DiscordServer(Base, MessageProcessor):
@ -130,6 +149,10 @@ class DiscordUser(Base, MessageProcessor):
__table_args__ = (Index("discord_users_system_user_idx", "system_user_id"),) __table_args__ = (Index("discord_users_system_user_idx", "system_user_id"),)
@property
def name(self) -> str:
return self.username
class MCPServer(Base): class MCPServer(Base):
"""MCP server configuration and OAuth state.""" """MCP server configuration and OAuth state."""
@ -164,6 +187,30 @@ class MCPServer(Base):
__table_args__ = (Index("mcp_state_idx", "state"),) __table_args__ = (Index("mcp_state_idx", "state"),)
def as_xml(self) -> str:
tools = "\n".join(f"{tool}" for tool in self.available_tools).strip()
return textwrap.dedent("""
<mcp_server>
<name>
{name}
</name>
<mcp_server_url>
{mcp_server_url}
</mcp_server_url>
<client_id>
{client_id}
</client_id>
<available_tools>
{available_tools}
</available_tools>
</mcp_server>
""").format(
name=self.name,
mcp_server_url=self.mcp_server_url,
client_id=self.client_id,
available_tools=tools,
)
class MCPServerAssignment(Base): class MCPServerAssignment(Base):
"""Assignment of MCP servers to entities (users, channels, servers, etc.).""" """Assignment of MCP servers to entities (users, channels, servers, etc.)."""

View File

@ -345,11 +345,12 @@ class DiscordMessage(SourceItem):
@property @property
def system_prompt(self) -> str: def system_prompt(self) -> str:
return ( prompts = [
(self.from_user and self.from_user.system_prompt) (self.from_user and self.from_user.system_prompt),
or (self.channel and self.channel.system_prompt) (self.channel and self.channel.system_prompt),
or (self.server and self.server.system_prompt) (self.server and self.server.system_prompt),
) ]
return "\n\n".join(p for p in prompts if p)
@property @property
def chattiness_threshold(self) -> int: def chattiness_threshold(self) -> int:

View File

@ -1,5 +1,6 @@
"""Lightweight slash-command helpers for the Discord collector.""" """Lightweight slash-command helpers for the Discord collector."""
import io
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from typing import Callable, Literal from typing import Callable, Literal
@ -8,12 +9,34 @@ import discord
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from memory.common.db.connection import make_session from memory.common.db.connection import make_session
from memory.common.db.models import DiscordChannel, DiscordServer, DiscordUser from memory.common.db.models import (
DiscordChannel,
DiscordServer,
DiscordUser,
MCPServer,
MCPServerAssignment,
)
from memory.discord.mcp import run_mcp_server_command from memory.discord.mcp import run_mcp_server_command
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ScopeLiteral = Literal["bot", "server", "channel", "user"] ScopeLiteral = Literal["bot", "me", "server", "channel", "user"]
@dataclass
class DiscordObjects:
bot: DiscordUser
server: DiscordServer | None
channel: DiscordChannel | None
user: DiscordUser | None
@property
def items(self):
items = [self.bot, self.server, self.channel, self.user]
return [item for item in items if item is not None]
ListHandler = Callable[[DiscordObjects], str]
class CommandError(Exception): class CommandError(Exception):
@ -43,12 +66,322 @@ class CommandContext:
CommandHandler = Callable[..., CommandResponse] CommandHandler = Callable[..., CommandResponse]
async def respond(
interaction: discord.Interaction, content: str, ephemeral: bool = True
) -> None:
"""Send a response to the interaction, as file if too large."""
max_length = 1900
if len(content) <= max_length:
await interaction.response.send_message(content, ephemeral=ephemeral)
return
file = discord.File(io.BytesIO(content.encode("utf-8")), filename="response.txt")
await interaction.response.send_message(
"Response too large, sending as file:", file=file, ephemeral=ephemeral
)
def with_object_context(
bot: discord.Client,
interaction: discord.Interaction,
handler: ListHandler,
user: discord.User | None,
) -> str:
"""Execute handler with Discord objects context."""
server = interaction.guild
channel = interaction.channel
target_user = user or interaction.user
with make_session() as session:
objects = DiscordObjects(
bot=ensure_user(session, bot.user),
server=server and ensure_server(session, server),
channel=channel and _ensure_channel(session, channel, server and server.id),
user=ensure_user(session, target_user),
)
return handler(objects)
def _create_scope_group(
parent: discord.app_commands.Group,
scope: ScopeLiteral,
name: str,
description: str,
) -> discord.app_commands.Group:
"""Create a command group for a scope (bot/me/server/channel).
Args:
parent: Parent command group
scope: Scope literal (bot, me, server, channel)
name: Group name
description: Group description
"""
group = discord.app_commands.Group(
name=name, description=description, parent=parent
)
@group.command(name="prompt", description=f"Manage {name}'s system prompt")
@discord.app_commands.describe(prompt="The system prompt to set")
async def prompt_cmd(interaction: discord.Interaction, prompt: str | None = None):
await _run_interaction_command(
interaction, scope=scope, handler=handle_prompt, prompt=prompt
)
@group.command(name="chattiness", description=f"Show/set {name}'s chattiness")
@discord.app_commands.describe(value="Optional new chattiness value (0-100)")
async def chattiness_cmd(
interaction: discord.Interaction, value: int | None = None
):
await _run_interaction_command(
interaction, scope=scope, handler=handle_chattiness, value=value
)
# Ignore command
@group.command(name="ignore", description=f"Toggle bot ignoring {name} messages")
@discord.app_commands.describe(enabled="Whether to ignore messages")
async def ignore_cmd(interaction: discord.Interaction, enabled: bool | None = None):
await _run_interaction_command(
interaction, scope=scope, handler=handle_ignore, ignore_enabled=enabled
)
# Summary command
@group.command(name="summary", description=f"Show {name}'s summary")
async def summary_cmd(interaction: discord.Interaction):
await _run_interaction_command(interaction, scope=scope, handler=handle_summary)
# MCP command
@group.command(name="mcp", description=f"Manage {name}'s MCP servers")
@discord.app_commands.describe(
action="Action to perform",
url="MCP server URL (required for add, delete, connect, tools)",
)
async def mcp_cmd(
interaction: discord.Interaction,
action: Literal["list", "add", "delete", "connect", "tools"] = "list",
url: str | None = None,
):
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_mcp_servers,
action=action,
url=url and url.strip(),
)
return group
def _create_user_scope_group(
parent: discord.app_commands.Group,
name: str,
description: str,
) -> discord.app_commands.Group:
"""Create command group for user scope (requires user parameter).
Args:
parent: Parent command group
name: Group name
description: Group description
"""
group = discord.app_commands.Group(
name=name, description=description, parent=parent
)
scope = "user"
@group.command(name="prompt", description=f"Manage {name}'s system prompt")
@discord.app_commands.describe(
user="Target user", prompt="The system prompt to set"
)
async def prompt_cmd(
interaction: discord.Interaction, user: discord.User, prompt: str | None = None
):
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_prompt,
target_user=user,
prompt=prompt,
)
@group.command(name="chattiness", description=f"Show/set {name}'s chattiness")
@discord.app_commands.describe(
user="Target user", value="Optional new chattiness value (0-100)"
)
async def chattiness_cmd(
interaction: discord.Interaction, user: discord.User, value: int | None = None
):
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_chattiness,
target_user=user,
value=value,
)
# Ignore command
@group.command(name="ignore", description=f"Toggle bot ignoring {name} messages")
@discord.app_commands.describe(
user="Target user", enabled="Whether to ignore messages"
)
async def ignore_cmd(
interaction: discord.Interaction,
user: discord.User,
enabled: bool | None = None,
):
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_ignore,
target_user=user,
ignore_enabled=enabled,
)
# Summary command
@group.command(name="summary", description=f"Show {name}'s summary")
@discord.app_commands.describe(user="Target user")
async def summary_cmd(interaction: discord.Interaction, user: discord.User):
await _run_interaction_command(
interaction, scope=scope, handler=handle_summary, target_user=user
)
# MCP command
@group.command(name="mcp", description=f"Manage {name}'s MCP servers")
@discord.app_commands.describe(
user="Target user",
action="Action to perform",
url="MCP server URL (required for add, delete, connect, tools)",
)
async def mcp_cmd(
interaction: discord.Interaction,
user: discord.User,
action: Literal["list", "add", "delete", "connect", "tools"] = "list",
url: str | None = None,
):
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_mcp_servers,
target_user=user,
action=action,
url=url and url.strip(),
)
return group
def create_list_group(
bot: discord.Client, parent: discord.app_commands.Group
) -> discord.app_commands.Group:
"""Create command group for listing settings.
Args:
parent: Parent command group
"""
group = discord.app_commands.Group(
name="list", description="List settings", parent=parent
)
@group.command(name="prompt", description="List full system prompt")
@discord.app_commands.describe(user="Target user")
async def prompt_cmd(
interaction: discord.Interaction, user: discord.User | None = None
):
def handler(objects: DiscordObjects) -> str:
prompts = [o.xml_prompt() for o in objects.items if o.system_prompt]
return "\n\n".join(prompts)
res = with_object_context(bot, interaction, handler, user)
await respond(interaction, res)
@group.command(name="chattiness", description="Show {name}'s chattiness")
@discord.app_commands.describe(user="Target user")
async def chattiness_cmd(
interaction: discord.Interaction, user: discord.User | None = None
):
def handler(objects: DiscordObjects) -> str:
values = [
o.chattiness_threshold
for o in objects.items
if o.chattiness_threshold is not None
]
val = min(values) if values else 50
if objects.user:
return f"Total current chattiness for {objects.user.username}: {val}"
return f"Total current chattiness: {val}"
res = with_object_context(bot, interaction, handler, user)
await respond(interaction, res)
@group.command(
name="ignore", description="Does this bot ignore messages for this user?"
)
@discord.app_commands.describe(user="Target user")
async def ignore_cmd(
interaction: discord.Interaction,
user: discord.User | None = None,
):
def handler(objects: DiscordObjects) -> str:
should_ignore = any(o.ignore_messages for o in objects.items)
if should_ignore:
return f"The bot ignores messages for {objects.user}."
return f"The bot does not ignore messages for {objects.user}."
res = with_object_context(bot, interaction, handler, user)
await respond(interaction, res)
@group.command(name="summary", description="Show the full summary")
@discord.app_commands.describe(user="Target user")
async def summary_cmd(
interaction: discord.Interaction, user: discord.User | None = None
):
def handler(objects: DiscordObjects) -> str:
summaries = [o.xml_summary() for o in objects.items if o.summary]
return "\n\n".join(summaries)
res = with_object_context(bot, interaction, handler, user)
await respond(interaction, res)
@group.command(name="mcp", description="All used MCP servers")
@discord.app_commands.describe(user="Target user")
async def mcp_cmd(
interaction: discord.Interaction, user: discord.User | None = None
):
logger.error(f"Listing MCP servers for {user}")
ids = [
interaction.guild_id,
interaction.channel_id,
(user or interaction.user).id,
bot.user.id,
]
with make_session() as session:
mcp_servers = (
session.query(MCPServer)
.filter(
MCPServerAssignment.entity_id.in_(i for i in ids if i is not None)
)
.all()
)
mcp_servers = [mcp_server.as_xml() for mcp_server in mcp_servers]
res = "\n\n".join(mcp_servers)
await respond(interaction, res)
# def handler(objects: DiscordObjects) -> str:
# servers = [s.as_xml() for obj in objects.items for s in obj.mcp_servers]
# return "\n\n".join(servers) if servers else "No MCP servers configured."
# try:
# res = with_object_context(bot, interaction, handler, user)
# except Exception as exc:
# logger.error(f"Error listing MCP servers: {exc}", exc_info=True)
# return CommandResponse(content="Error listing MCP servers.")
# await respond(interaction, res)
return group
def register_slash_commands(bot: discord.Client) -> None: def register_slash_commands(bot: discord.Client) -> None:
"""Register the collector slash commands on the provided bot. """Register the collector slash commands on the provided bot.
Args: Args:
bot: Discord bot client bot: Discord bot client
name: Prefix for command names (e.g., "memory" creates "memory_prompt")
""" """
if getattr(bot, "_memory_commands_registered", False): if getattr(bot, "_memory_commands_registered", False):
@ -62,139 +395,21 @@ def register_slash_commands(bot: discord.Client) -> None:
tree = bot.tree tree = bot.tree
name = bot.user and bot.user.name.replace("-", "_").lower() name = bot.user and bot.user.name.replace("-", "_").lower()
@tree.command( # Create main command group
name=f"{name}_show_prompt", description="Show the current system prompt" memory_group = discord.app_commands.Group(
name=name or "memory", description=f"{name} bot configuration and management"
) )
@discord.app_commands.describe(
scope="Which configuration to inspect",
user="Target user when the scope is 'user'",
)
async def show_prompt_command(
interaction: discord.Interaction,
scope: ScopeLiteral,
user: discord.User | None = None,
) -> None:
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_prompt,
target_user=user,
)
@tree.command( # Create scope groups
name=f"{name}_set_prompt", _create_scope_group(memory_group, "bot", "bot", "Bot-wide settings")
description="Set the system prompt for the target", _create_scope_group(memory_group, "me", "me", "Your personal settings")
) _create_scope_group(memory_group, "server", "server", "Server-wide settings")
@discord.app_commands.describe( _create_scope_group(memory_group, "channel", "channel", "Channel-specific settings")
scope="Which configuration to modify", _create_user_scope_group(memory_group, "user", "Manage other users' settings")
prompt="The system prompt to set", create_list_group(bot, memory_group)
user="Target user when the scope is 'user'",
)
async def set_prompt_command(
interaction: discord.Interaction,
scope: ScopeLiteral,
prompt: str,
user: discord.User | None = None,
) -> None:
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_set_prompt,
target_user=user,
prompt=prompt,
)
@tree.command( # Register main group
name=f"{name}_chattiness", tree.add_command(memory_group)
description="Show or update the chattiness for the target",
)
@discord.app_commands.describe(
scope="Which configuration to inspect",
value="Optional new chattiness value between 0 and 100",
user="Target user when the scope is 'user'",
)
async def chattiness_command(
interaction: discord.Interaction,
scope: ScopeLiteral,
value: int | None = None,
user: discord.User | None = None,
) -> None:
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_chattiness,
target_user=user,
value=value,
)
@tree.command(
name=f"{name}_ignore",
description="Toggle whether the bot should ignore messages for the target",
)
@discord.app_commands.describe(
scope="Which configuration to modify",
enabled="Optional flag. Leave empty to enable ignoring.",
user="Target user when the scope is 'user'",
)
async def ignore_command(
interaction: discord.Interaction,
scope: ScopeLiteral,
enabled: bool | None = None,
user: discord.User | None = None,
) -> None:
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_ignore,
target_user=user,
ignore_enabled=enabled,
)
@tree.command(
name=f"{name}_show_summary",
description="Show the stored summary for the target",
)
@discord.app_commands.describe(
scope="Which configuration to inspect",
user="Target user when the scope is 'user'",
)
async def summary_command(
interaction: discord.Interaction,
scope: ScopeLiteral,
user: discord.User | None = None,
) -> None:
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_summary,
target_user=user,
)
@tree.command(
name=f"{name}_mcp_servers",
description="Manage MCP servers for a scope",
)
@discord.app_commands.describe(
scope="Which configuration to modify (server, channel, or user)",
action="Action to perform",
url="MCP server URL (required for add, delete, connect, tools)",
user="Target user when the scope is 'user'",
)
async def mcp_servers_command(
interaction: discord.Interaction,
scope: ScopeLiteral,
action: Literal["list", "add", "delete", "connect", "tools"] = "list",
url: str | None = None,
user: discord.User | None = None,
) -> None:
await _run_interaction_command(
interaction,
scope=scope,
handler=handle_mcp_servers,
target_user=user,
action=action,
url=url and url.strip(),
)
async def _run_interaction_command( async def _run_interaction_command(
@ -208,17 +423,16 @@ async def _run_interaction_command(
"""Shared coroutine used by the registered slash commands.""" """Shared coroutine used by the registered slash commands."""
try: try:
with make_session() as session: with make_session() as session:
context = _build_context(session, interaction, scope, target_user) # Get bot from interaction client if needed for bot scope
bot = getattr(interaction, "client", None)
context = _build_context(session, interaction, scope, target_user, bot)
response = await handler(context, **handler_kwargs) response = await handler(context, **handler_kwargs)
session.commit() session.commit()
except CommandError as exc: # pragma: no cover - passthrough except CommandError as exc: # pragma: no cover - passthrough
await interaction.response.send_message(str(exc), ephemeral=True) await respond(interaction, str(exc))
return return
await interaction.response.send_message( await respond(interaction, response.content, response.ephemeral)
response.content,
ephemeral=response.ephemeral,
)
def _build_context( def _build_context(
@ -226,60 +440,55 @@ def _build_context(
interaction: discord.Interaction, interaction: discord.Interaction,
scope: ScopeLiteral, scope: ScopeLiteral,
target_user: discord.User | None, target_user: discord.User | None,
bot: discord.Client | None = None,
) -> CommandContext: ) -> CommandContext:
actor = _ensure_user(session, interaction.user) actor = ensure_user(session, interaction.user)
if scope == "server": # Determine target and display name based on scope
if scope == "bot":
if not bot or not bot.user:
raise CommandError("Bot user is not available.")
target = ensure_user(session, bot.user)
display_name = f"bot **{bot.user.name}**"
elif scope == "me":
target = ensure_user(session, interaction.user)
name = target.display_name or target.username
display_name = f"you (**{name}**)"
elif scope == "server":
if interaction.guild is None: if interaction.guild is None:
raise CommandError("This command can only be used inside a server.") raise CommandError("This command can only be used inside a server.")
target = ensure_server(session, interaction.guild)
target = _ensure_server(session, interaction.guild)
display_name = f"server **{target.name}**" display_name = f"server **{target.name}**"
return CommandContext(
session=session,
interaction=interaction,
actor=actor,
scope=scope,
target=target,
display_name=display_name,
)
if scope == "channel": elif scope == "channel":
channel_obj = interaction.channel if interaction.channel is None or not hasattr(interaction.channel, "id"):
if channel_obj is None or not hasattr(channel_obj, "id"):
raise CommandError("Unable to determine channel for this interaction.") raise CommandError("Unable to determine channel for this interaction.")
target = _ensure_channel(session, interaction.channel, interaction.guild_id)
target = _ensure_channel(session, channel_obj, interaction.guild_id)
display_name = f"channel **#{target.name}**" display_name = f"channel **#{target.name}**"
return CommandContext(
session=session,
interaction=interaction,
actor=actor,
scope=scope,
target=target,
display_name=display_name,
)
if scope == "user": elif scope == "user":
discord_user = target_user or interaction.user if target_user is None:
if discord_user is None:
raise CommandError("A target user is required for this command.") raise CommandError("A target user is required for this command.")
target = ensure_user(session, target_user)
name = target.display_name or target.username
display_name = f"user **{name}**"
target = _ensure_user(session, discord_user) else:
display_name = target.display_name or target.username raise CommandError(f"Unsupported scope '{scope}'.")
return CommandContext(
session=session,
interaction=interaction,
actor=actor,
scope=scope,
target=target,
display_name=f"user **{display_name}**",
)
raise CommandError(f"Unsupported scope '{scope}'.") return CommandContext(
session=session,
interaction=interaction,
actor=actor,
scope=scope,
target=target,
display_name=display_name,
)
def _ensure_server(session: Session, guild: discord.Guild) -> DiscordServer: def ensure_server(session: Session, guild: discord.Guild) -> DiscordServer:
server = session.get(DiscordServer, guild.id) server = session.get(DiscordServer, guild.id)
if server is None: if server is None:
server = DiscordServer( server = DiscordServer(
@ -330,7 +539,7 @@ def _ensure_channel(
return channel_model return channel_model
def _ensure_user(session: Session, discord_user: discord.abc.User) -> DiscordUser: def ensure_user(session: Session, discord_user: discord.abc.User) -> DiscordUser:
user = session.get(DiscordUser, discord_user.id) user = session.get(DiscordUser, discord_user.id)
display_name = getattr(discord_user, "display_name", discord_user.name) display_name = getattr(discord_user, "display_name", discord_user.name)
if user is None: if user is None:
@ -364,32 +573,23 @@ def _resolve_channel_type(channel: discord.abc.Messageable) -> str:
return getattr(getattr(channel, "type", None), "name", "unknown") return getattr(getattr(channel, "type", None), "name", "unknown")
def handle_prompt(context: CommandContext) -> CommandResponse: async def handle_prompt(
prompt = getattr(context.target, "system_prompt", None) context: CommandContext, *, prompt: str | None = None
) -> CommandResponse:
if prompt is not None:
prompt = prompt or None
setattr(context.target, "system_prompt", prompt)
else:
prompt = getattr(context.target, "system_prompt", None)
if prompt: if prompt:
return CommandResponse( content = f"Current prompt for {context.display_name}:\n\n{prompt}"
content=f"Current prompt for {context.display_name}:\n\n{prompt}", else:
) content = f"No prompt configured for {context.display_name}."
return CommandResponse(content=content)
return CommandResponse(
content=f"No prompt configured for {context.display_name}.",
)
def handle_set_prompt( async def handle_chattiness(
context: CommandContext,
*,
prompt: str,
) -> CommandResponse:
setattr(context.target, "system_prompt", prompt)
return CommandResponse(
content=f"Updated system prompt for {context.display_name}.",
)
def handle_chattiness(
context: CommandContext, context: CommandContext,
*, *,
value: int | None, value: int | None,
@ -419,7 +619,7 @@ def handle_chattiness(
) )
def handle_ignore( async def handle_ignore(
context: CommandContext, context: CommandContext,
*, *,
ignore_enabled: bool | None, ignore_enabled: bool | None,
@ -434,7 +634,7 @@ def handle_ignore(
) )
def handle_summary(context: CommandContext) -> CommandResponse: async def handle_summary(context: CommandContext) -> CommandResponse:
summary = getattr(context.target, "summary", None) summary = getattr(context.target, "summary", None)
if summary: if summary:
@ -454,20 +654,22 @@ async def handle_mcp_servers(
url: str | None, url: str | None,
) -> CommandResponse: ) -> CommandResponse:
"""Handle MCP server commands for a specific scope.""" """Handle MCP server commands for a specific scope."""
entity_type_map = { # Map scope to database entity type
entity_type = {
"bot": "DiscordUser",
"me": "DiscordUser",
"user": "DiscordUser",
"server": "DiscordServer", "server": "DiscordServer",
"channel": "DiscordChannel", "channel": "DiscordChannel",
"user": "DiscordUser", }[context.scope]
}
entity_type = entity_type_map[context.scope] bot_user = getattr(getattr(context.interaction, "client", None), "user", None)
entity_id = context.target.id
try: try:
res = await run_mcp_server_command( res = await run_mcp_server_command(
context.interaction.user, action, url, entity_type, entity_id bot_user, action, url, entity_type, context.target.id
) )
return CommandResponse(content=res) return CommandResponse(content=res)
except Exception as exc: except Exception as exc:
import traceback logger.error(f"Error running MCP server command: {exc}", exc_info=True)
logger.error(f"Error running MCP server command: {traceback.format_exc()}")
raise CommandError(f"Error: {exc}") from exc raise CommandError(f"Error: {exc}") from exc

View File

@ -210,6 +210,11 @@ def process_discord_message(message_id: int) -> dict[str, Any]:
for server in discord_message.recipient_user.mcp_servers for server in discord_message.recipient_user.mcp_servers
] ]
system_prompt = discord_message.system_prompt or ""
system_prompt += comm_channel_prompt(
session, discord_message.recipient_user, discord_message.channel
)
try: try:
response = call_llm( response = call_llm(
session, session,