Skip to content

feat: add onboarding Discord and email raw tools#57

Merged
emsearcy merged 8 commits intomainfrom
onboarding-tools-discord-email
Apr 14, 2026
Merged

feat: add onboarding Discord and email raw tools#57
emsearcy merged 8 commits intomainfrom
onboarding-tools-discord-email

Conversation

@joanreyero
Copy link
Copy Markdown
Contributor

Summary

  • Add 6 Discord tools (onboarding_tools_discord_*): get_config, list_roles, find_role, find_user, check_user_role, assign_role
  • Add 3 email tools (onboarding_tools_email_*): list_templates, render_template, send
  • Rename onboarding_list_membershipsonboarding_guided_list_memberships to separate guided vs raw tool namespaces
  • All tools proxy to the member-onboarding service via M2M client credentials auth

Test plan

  • Verify Discord tools proxy correctly to member-onboarding/tools/discord/ endpoints
  • Verify email tools proxy correctly to member-onboarding/tools/email/ endpoints
  • Confirm onboarding_guided_list_memberships still returns dry-run stub
  • Confirm access check is enforced on all new tools

🤖 Generated with Claude Code

Add 9 new onboarding tools that proxy to the member-onboarding service
API for Discord and email operations:

Discord (onboarding_tools_discord_*):
- get_config, list_roles, find_role, find_user, check_user_role, assign_role

Email (onboarding_tools_email_*):
- list_templates, render_template, send

Rename onboarding_list_memberships to onboarding_guided_list_memberships
to distinguish guided-flow tools from raw tools.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Joan Reyero <joan@reyero.io>
Copilot AI review requested due to automatic review settings April 9, 2026 13:45
Copy link
Copy Markdown

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new “raw” onboarding tool surface area for Discord and email by proxying requests through the member-onboarding service, and renames the existing memberships tool to a “guided” namespace to separate future guided vs raw functionality.

Changes:

  • Renames onboarding_list_memberships to onboarding_guided_list_memberships (tool name + registration).
  • Adds Discord onboarding tools for config lookup, role/user discovery, role checks, and role assignment (service-proxied).
  • Adds email onboarding tools for listing templates, rendering previews, and sending emails (service-proxied).

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
internal/tools/onboarding.go Renames the guided memberships tool registration and updates related comments.
internal/tools/onboarding_tools_discord.go Introduces Discord “raw tools” that proxy to /member-onboarding/tools/discord/....
internal/tools/onboarding_tools_email.go Introduces email “raw tools” that proxy to /member-onboarding/tools/email/....
cmd/lfx-mcp-server/main.go Enables the renamed tool and adds the new tools to defaults + server registration.
Comments suppressed due to low confidence (1)

internal/tools/onboarding.go:49

  • The tool was renamed to onboarding_guided_list_memberships, but the associated Go identifiers remain generic (OnboardingListMembershipsArgs, handleOnboardingListMemberships). If a non-guided/raw memberships tool is added later (as implied by the new namespace split), these names will collide conceptually and make the code harder to navigate. Consider renaming the args/handler to include Guided for clarity.
// OnboardingListMembershipsArgs defines the input for onboarding_guided_list_memberships.
type OnboardingListMembershipsArgs struct {
	ProjectSlug string `json:"project_slug" jsonschema:"Project slug from search_projects (e.g. 'agentic-ai-foundation')"`
	Status      string `json:"status,omitempty" jsonschema:"Filter by status,enum=all,enum=pending,enum=in_progress,enum=closed"`
}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/tools/onboarding_tools_discord.go Outdated
Comment thread internal/tools/onboarding_tools_discord.go Outdated
Comment thread internal/tools/onboarding_tools_email.go Outdated
Comment thread internal/tools/onboarding_tools_email.go Outdated
…rr-5gcm-pp58

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Joan Reyero <joan@reyero.io>
Align all tool names with verb-first naming used by the rest of the
codebase and the GitHub MCP server:

- onboarding_tools_discord_* → {verb}_onboarding_discord_*
- onboarding_tools_email_* → {verb}_onboarding_email_*
- onboarding_guided_list_memberships → list_membership_actions
- lfx_lens_query → query_lfx_lens

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Joan Reyero <joan@reyero.io>
@joanreyero joanreyero requested a review from emsearcy April 9, 2026 15:19
…ctive

- PathEscape user_id and role_id in check_user_role to prevent path injection
- Set DestructiveHint to true on send_onboarding_email (irreversible)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Joan Reyero <joan@reyero.io>
…riteScopes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Joan Reyero <joan@reyero.io>
Copy link
Copy Markdown
Contributor

@emsearcy emsearcy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some substantial questions around providing what appears to be Discord IT Services Management capability but calling it part of the onboarding API.

Comment thread cmd/lfx-mcp-server/main.go Outdated
Comment thread internal/tools/lens.go Outdated
// --- Tool args ---

// LFXLensQueryArgs defines the input for lfx_lens_query.
// LFXLensQueryArgs defines the input for query_lfx_lens.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we also rename the structs to match? (similar to #56, which I've closed out in favor of this PR)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — renamed LFXLensQueryArgs to QueryLFXLensArgs to match the tool name convention.

Comment thread internal/tools/onboarding.go Outdated
// --- Tool args ---

// OnboardingListMembershipsArgs defines the input for onboarding_list_memberships.
// OnboardingListMembershipsArgs defines the input for list_membership_actions.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also missed renaming the struct here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moot now — the tool and its struct have been removed entirely (see reply to the other comment).

Comment thread internal/tools/onboarding.go Outdated
Annotations: &mcp.ToolAnnotations{
Title: "List Onboarding Memberships",
ReadOnlyHint: true,
DestructiveHint: boolPtr(false),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand this (and perhaps, it might even be better if we just removed this tool until we have the actual working use case?)

If this is a "invoke onboarding actions" (maybe with a dry-run?), then it would be destructive (destructive is anything that cannot be reverted/undone). If this is more of a "see status of previously-invoked actions: pending/success/failure" ... then how would it not be readonly?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — removed the stub tool entirely. The registration, args struct, and handler are all gone. Left a TODO comment pointing to the member-onboarding service endpoint for when we're ready to add it back.

func RegisterGetOnboardingDiscordConfig(server *mcp.Server) {
AddServiceTool(server, &mcp.Tool{
Name: "get_onboarding_discord_config",
Description: "Check if Discord is configured for a project. IMPORTANT: Always call this before using any other *_onboarding_discord_* tool. If configured is false, tell the user they need to set up Discord in the LFX Project Control Center (PCC) first.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not wholly against this, but it doesn't seem like a typical pattern I've seen in other MCPs. Why not just let the user attempt to call the onboarding tool and have it fail with a helpful message: "Discord onboarding not available; please set up Discord in LFX Project Control Panel first"?

Also—and I realize we can change this later—but this would also get into "shouldn't we just have IT Collaboration Services tools that provide information and management of PCC-connected services" as their own tools, rather than calling them "onboarding config" tools.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — removed get_discord_config entirely. The API now returns a 404 with a helpful detail message when Discord/Email isn't configured for a project, so every tool handles this gracefully:

  • 404 responses are parsed for the FastAPI {"detail": "..."} format and returned as an IsError tool result, so the LLM can relay the message directly to the user (e.g. "Discord not available; please set up Discord in LFX Project Control Panel first.")
  • All "Depends on: get_discord_config" references removed from descriptions
  • Same 404 handling added to email tools

This keeps the tool surface area smaller and follows the fail-with-helpful-message pattern you suggested.

func RegisterListOnboardingDiscordRoles(server *mcp.Server) {
AddServiceTool(server, &mcp.Tool{
Name: "list_onboarding_discord_roles",
Description: "List all roles in the project's Discord guild. Use this to discover what roles exist before assigning one, or when the user asks about the role structure. Depends on: get_onboarding_discord_config (call first).",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this doesn't sound like an onboarding tool. this just sounds like a Discord "IT Services" abstraction capability. Why is the onboarding API providing this? Is it wrapping v1 Project Infrastructure Service? Can we present it to the user as an "IT Service" feature rather than an "onboarding" feature?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — dropped the "onboarding" prefix from all user-facing tool names. They're now presented as IT service capabilities:

Discord: get_discord_config, list_discord_roles, find_discord_role, find_discord_user, check_discord_user_role, assign_discord_role

Email: list_email_templates, render_email_template, send_email

Files renamed: onboarding_tools_discord.godiscord.go, onboarding_tools_email.goemail.go. Go function names updated to match. Internal implementation still references the onboarding service (since that's the backing API), but the tool surface area is now service-agnostic.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not seeing these changes (yet) in the PR

func RegisterSendOnboardingEmail(server *mcp.Server) {
AddServiceTool(server, &mcp.Tool{
Name: "send_onboarding_email",
Description: "Send a templated email to a recipient via AWS SES. Use this to send onboarding or welcome emails to key contacts or members. Always render the template first to confirm content before sending. Depends on: render_onboarding_email_template (call first to preview), list_onboarding_email_templates (for template_name).",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the template purely deterministic? even if it isn't it seems like it's not deterministic that the tool calls will be deterministic, since the agent calling them is not. IMO the only way you could really have that is by having a "draft" action which stages the email, and returns both the preview and some kind of hash to the user, and then a "send". this might even be able to be done with a single tool: mode: draft/send, where "send" requires the draft_hash from mode:draft.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we don't need to say "via AWS SES". Just "sends via LFX mail servers" or something.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — changed to "via LFX mail servers".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — collapsed render_email_template and send_email into a single send_email tool with a mode field (draft / send):

  • mode=draft: renders a preview via the render endpoint, no email sent
  • mode=send: delivers the email (requires to_email and to_name)

The description instructs the caller to always draft first. This reduces the tool surface by one and keeps the same two-step safety flow. The hash-based confirmation is a known limitation of proxying through the onboarding API as-is — we can revisit when we have more control over the API contract.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

your comment is good, but I don't see the code that actually implements this change

joanreyero and others added 3 commits April 10, 2026 15:04
Resolve go.mod conflicts by taking newer OTel versions from main and
promoting go.opentelemetry.io/otel/trace to a direct dependency.

🤖 Generated with [GitHub Copilot](https://github.com/features/copilot) (via OpenCode)

Signed-off-by: Eric Searcy <eric@linuxfoundation.org>
- Drop "onboarding" prefix from all Discord/email tool names and present
  them as IT service capabilities (discord.go, email.go)
- Remove get_discord_config pre-check tool; all tools now handle 404
  with the FastAPI detail message (e.g. "Discord not available; please
  set up Discord in LFX Project Control Panel first")
- Remove list_membership_actions stub tool entirely (re-add when guided
  onboarding flow is ready)
- Collapse render_email_template + send_email into a single send_email
  tool with mode=draft/send
- Rename LFXLensQueryArgs → QueryLFXLensArgs to match tool naming
- Change "via AWS SES" → "via LFX mail servers" in email description

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Signed-off-by: Joan Reyero <joan@reyero.io>
@emsearcy emsearcy merged commit 8ba3bba into main Apr 14, 2026
7 checks passed
@emsearcy emsearcy deleted the onboarding-tools-discord-email branch April 14, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants