From 39afaf018b02fa4e024cf9be94a22d40db3ac06a Mon Sep 17 00:00:00 2001 From: Eric Searcy Date: Fri, 10 Apr 2026 12:55:01 -0700 Subject: [PATCH] feat: update Go 1.26.2, bump all deps, fix mailing list service v0.4.7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update go.mod to Go 1.26.2 and run go get -u on all dependencies. Adapt to the mailing-list-service v0.4.7 rewrite (Grpsioβ†’Groupsio rename, wrapper-struct removal, UIDβ†’ServiceID/SubgroupID/MemberID field renames, settings endpoints removed, 6 new endpoints added). Add 4 new committee-service document endpoints (v0.2.29). Move otel/trace to direct dependency. Align markdown tables with MegaLinter markdownlint, add megalinter Makefile target, and shfmt-format shell scripts. ARCH-388 πŸ€– Generated with [GitHub Copilot](https://github.com/features/copilot) (via OpenCode) Signed-off-by: Eric Searcy --- AGENTS.md | 62 ++++---- DEVELOPER.md | 62 ++++---- Makefile | 8 +- README.md | 88 ++++++------ docs/service-api-architecture.md | 92 ++++++------ go.mod | 56 ++++---- go.sum | 112 +++++++-------- internal/lfxv2/client.go | 44 +++--- internal/tools/mailing_list.go | 142 ++++++------------- scripts/test_oauth_config.sh | 78 +++++----- scripts/test_oauth_flow.sh | 236 +++++++++++++++---------------- scripts/test_prm.sh | 8 +- scripts/test_server.sh | 6 +- 13 files changed, 478 insertions(+), 516 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6d5f5da..cc68a3a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -167,13 +167,13 @@ func handleMyTool(ctx context.Context, req *mcp.CallToolRequest, args MyToolArgs **Key differences:** -| Feature | Server Logging | MCP Client Logging | -|---------|---------------|-------------------| -| Audience | Server operators | Client users/developers | -| Output | stdout/stderr | MCP protocol notifications | -| Control | `-debug` flag | Client's `SetLoggingLevel` | -| Format | JSON to files | JSON over protocol | -| Use case | Debugging server | Debugging tool execution | +| Feature | Server Logging | MCP Client Logging | +|----------|------------------|----------------------------| +| Audience | Server operators | Client users/developers | +| Output | stdout/stderr | MCP protocol notifications | +| Control | `-debug` flag | Client's `SetLoggingLevel` | +| Format | JSON to files | JSON over protocol | +| Use case | Debugging server | Debugging tool execution | ### Server Log Structure @@ -232,9 +232,9 @@ The MCP Go SDK provides a simple pattern for adding tools. Tools are implemented Two scope constants are defined in `internal/tools/scopes.go`: -| Constant | Value | Used for | -|----------|-------|----------| -| `ScopeRead` | `read:all` | Tools with `ReadOnlyHint: true` | +| Constant | Value | Used for | +|---------------|--------------|-----------------------------------------------------| +| `ScopeRead` | `read:all` | Tools with `ReadOnlyHint: true` | | `ScopeManage` | `manage:all` | Tools where `ReadOnlyHint` is `false` (the default) | Use the helper functions `ReadScopes()` and `WriteScopes()` when registering tools. @@ -488,27 +488,27 @@ The server supports configuration via environment variables with the `LFXMCP_` p **Configuration Precedence:** Environment variables **override** command-line flags. This allows command-line flags to provide defaults while environment variables can override them in containerized deployments. -| Variable | Description | Default | Required | -|----------|-------------|---------|----------| -| `LFXMCP_MODE` | Transport mode (`stdio` or `http`) | stdio | No | -| `LFXMCP_HTTP_HOST` | HTTP server host | 127.0.0.1 | No | -| `LFXMCP_HTTP_PORT` | HTTP server port | 8080 | No | -| `LFXMCP_HTTP_PUBLIC_URL` | Public URL for HTTP transport | - | No | -| `LFXMCP_DEBUG` | Enable debug logging | false | No | -| `LFXMCP_DEBUG_TRAFFIC` | Enable HTTP request/response wire logging for outbound LFX API calls | false | No | -| `LFXMCP_TOOLS` | Comma-separated list of tools to enable | - | No | -| `LFXMCP_MCP_API_AUTH_SERVERS` | Comma-separated list of authorization server URLs | - | No | -| `LFXMCP_MCP_API_PUBLIC_URL` | Public URL for MCP API (for OAuth PRM) | - | No | -| `LFXMCP_MCP_API_SCOPES` | OAuth scopes as comma-separated list | - | No | -| `LFXMCP_CLIENT_ID` | OAuth client ID for authentication | - | No | -| `LFXMCP_CLIENT_SECRET` | OAuth client secret | - | No | -| `LFXMCP_CLIENT_ASSERTION_SIGNING_KEY` | PEM-encoded RSA private key for client assertion | - | No | -| `LFXMCP_TOKEN_ENDPOINT` | OAuth2 token endpoint URL for token exchange | - | No | -| `LFXMCP_LFX_API_URL` | LFX API URL (used as token exchange audience) | - | No | -| `LFXMCP_ONBOARDING_API_URL` | Base URL of the member onboarding service | - | No | -| `LFXMCP_ONBOARDING_API_AUDIENCE` | Auth0 resource server audience for the member onboarding API | - | No | -| `LFXMCP_LENS_API_URL` | Base URL of the LFX Lens service | - | No | -| `LFXMCP_LENS_API_AUDIENCE` | Auth0 resource server audience for the LFX Lens API | - | No | +| Variable | Description | Default | Required | +|---------------------------------------|----------------------------------------------------------------------|-----------|----------| +| `LFXMCP_MODE` | Transport mode (`stdio` or `http`) | stdio | No | +| `LFXMCP_HTTP_HOST` | HTTP server host | 127.0.0.1 | No | +| `LFXMCP_HTTP_PORT` | HTTP server port | 8080 | No | +| `LFXMCP_HTTP_PUBLIC_URL` | Public URL for HTTP transport | - | No | +| `LFXMCP_DEBUG` | Enable debug logging | false | No | +| `LFXMCP_DEBUG_TRAFFIC` | Enable HTTP request/response wire logging for outbound LFX API calls | false | No | +| `LFXMCP_TOOLS` | Comma-separated list of tools to enable | - | No | +| `LFXMCP_MCP_API_AUTH_SERVERS` | Comma-separated list of authorization server URLs | - | No | +| `LFXMCP_MCP_API_PUBLIC_URL` | Public URL for MCP API (for OAuth PRM) | - | No | +| `LFXMCP_MCP_API_SCOPES` | OAuth scopes as comma-separated list | - | No | +| `LFXMCP_CLIENT_ID` | OAuth client ID for authentication | - | No | +| `LFXMCP_CLIENT_SECRET` | OAuth client secret | - | No | +| `LFXMCP_CLIENT_ASSERTION_SIGNING_KEY` | PEM-encoded RSA private key for client assertion | - | No | +| `LFXMCP_TOKEN_ENDPOINT` | OAuth2 token endpoint URL for token exchange | - | No | +| `LFXMCP_LFX_API_URL` | LFX API URL (used as token exchange audience) | - | No | +| `LFXMCP_ONBOARDING_API_URL` | Base URL of the member onboarding service | - | No | +| `LFXMCP_ONBOARDING_API_AUDIENCE` | Auth0 resource server audience for the member onboarding API | - | No | +| `LFXMCP_LENS_API_URL` | Base URL of the LFX Lens service | - | No | +| `LFXMCP_LENS_API_AUDIENCE` | Auth0 resource server audience for the LFX Lens API | - | No | **Example:** diff --git a/DEVELOPER.md b/DEVELOPER.md index 70c0d74..a23db9e 100644 --- a/DEVELOPER.md +++ b/DEVELOPER.md @@ -40,23 +40,23 @@ The HTTP server listens at `http://localhost:8080/mcp` by default. Environment variables use the `LFXMCP_` prefix and **override** their corresponding flags. -| Flag | Env Var | Default | Description | -|------|---------|---------|-------------| -| `-mode` | `LFXMCP_MODE` | `stdio` | Transport mode: `stdio` or `http` | -| `-http.host` | `LFXMCP_HTTP_HOST` | `127.0.0.1` | HTTP server bind address | -| `-http.port` | `LFXMCP_HTTP_PORT` | `8080` | HTTP server port | -| `-http.public_url` | `LFXMCP_HTTP_PUBLIC_URL` | β€” | Public URL for HTTP transport (reverse proxies) | -| `-debug` | `LFXMCP_DEBUG` | `false` | Enable debug logging with source locations | -| `-debug_traffic` | `LFXMCP_DEBUG_TRAFFIC` | `false` | Log outbound LFX API request/response bodies | -| `-tools` | `LFXMCP_TOOLS` | β€” | Comma-separated list of tools to enable | -| `-mcp_api.auth_servers` | `LFXMCP_MCP_API_AUTH_SERVERS` | β€” | OAuth authorization server URLs (comma-separated) | -| `-mcp_api.public_url` | `LFXMCP_MCP_API_PUBLIC_URL` | β€” | Public URL for MCP API (OAuth PRM) | -| `-mcp_api.scopes` | `LFXMCP_MCP_API_SCOPES` | β€” | OAuth scopes (comma-separated) | -| `-client_id` | `LFXMCP_CLIENT_ID` | β€” | OAuth client ID for token exchange | -| `-client_secret` | `LFXMCP_CLIENT_SECRET` | β€” | OAuth client secret | -| `-client_assertion_signing_key` | `LFXMCP_CLIENT_ASSERTION_SIGNING_KEY` | β€” | PEM-encoded RSA private key for client assertion (RFC 7523) | -| `-token_endpoint` | `LFXMCP_TOKEN_ENDPOINT` | β€” | OAuth2 token endpoint URL (RFC 8693) | -| `-lfx_api_url` | `LFXMCP_LFX_API_URL` | β€” | LFX API base URL (token exchange audience) | +| Flag | Env Var | Default | Description | +|---------------------------------|---------------------------------------|-------------|-------------------------------------------------------------| +| `-mode` | `LFXMCP_MODE` | `stdio` | Transport mode: `stdio` or `http` | +| `-http.host` | `LFXMCP_HTTP_HOST` | `127.0.0.1` | HTTP server bind address | +| `-http.port` | `LFXMCP_HTTP_PORT` | `8080` | HTTP server port | +| `-http.public_url` | `LFXMCP_HTTP_PUBLIC_URL` | β€” | Public URL for HTTP transport (reverse proxies) | +| `-debug` | `LFXMCP_DEBUG` | `false` | Enable debug logging with source locations | +| `-debug_traffic` | `LFXMCP_DEBUG_TRAFFIC` | `false` | Log outbound LFX API request/response bodies | +| `-tools` | `LFXMCP_TOOLS` | β€” | Comma-separated list of tools to enable | +| `-mcp_api.auth_servers` | `LFXMCP_MCP_API_AUTH_SERVERS` | β€” | OAuth authorization server URLs (comma-separated) | +| `-mcp_api.public_url` | `LFXMCP_MCP_API_PUBLIC_URL` | β€” | Public URL for MCP API (OAuth PRM) | +| `-mcp_api.scopes` | `LFXMCP_MCP_API_SCOPES` | β€” | OAuth scopes (comma-separated) | +| `-client_id` | `LFXMCP_CLIENT_ID` | β€” | OAuth client ID for token exchange | +| `-client_secret` | `LFXMCP_CLIENT_SECRET` | β€” | OAuth client secret | +| `-client_assertion_signing_key` | `LFXMCP_CLIENT_ASSERTION_SIGNING_KEY` | β€” | PEM-encoded RSA private key for client assertion (RFC 7523) | +| `-token_endpoint` | `LFXMCP_TOKEN_ENDPOINT` | β€” | OAuth2 token endpoint URL (RFC 8693) | +| `-lfx_api_url` | `LFXMCP_LFX_API_URL` | β€” | LFX API base URL (token exchange audience) | ## Code Quality @@ -208,17 +208,17 @@ For more detail on tool annotations, JSON schema tags, content types, and the MC ## Build System -| Target | Description | -|--------|-------------| -| `all` | Clean, check, and build (default) | -| `build` | Compile the binary | -| `clean` | Remove build artifacts | -| `fmt` | Format Go code | -| `vet` | Run go vet | -| `lint` | Run golangci-lint | -| `check` | Run fmt, vet, and lint | -| `run` | Build and run in stdio mode | -| `test` | Run Go tests | -| `test-coverage` | Run tests with coverage | -| `deps` | Download and tidy dependencies | -| `install-tools` | Install development tools | \ No newline at end of file +| Target | Description | +|-----------------|-----------------------------------| +| `all` | Clean, check, and build (default) | +| `build` | Compile the binary | +| `clean` | Remove build artifacts | +| `fmt` | Format Go code | +| `vet` | Run go vet | +| `lint` | Run golangci-lint | +| `check` | Run fmt, vet, and lint | +| `run` | Build and run in stdio mode | +| `test` | Run Go tests | +| `test-coverage` | Run tests with coverage | +| `deps` | Download and tidy dependencies | +| `install-tools` | Install development tools | \ No newline at end of file diff --git a/Makefile b/Makefile index 6782532..d557f88 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Copyright The Linux Foundation and each contributor to LFX. # SPDX-License-Identifier: MIT -.PHONY: all build clean check fmt vet lint test test-coverage run help deps install-tools docker-build ko-build +.PHONY: all build clean check fmt vet lint test test-coverage run help deps install-tools docker-build ko-build megalinter # Build variables BINARY_NAME=lfx-mcp-server @@ -107,6 +107,11 @@ ko-build: @echo "Building ko image..." KO_DOCKER_REPO=$(DOCKER_IMAGE) VERSION=$(VERSION) ko build -L --bare --tags local ./cmd/lfx-mcp-server +# Run MegaLinter locally via Docker (matches CI Go flavor at v9). +megalinter: + docker pull oxsecurity/megalinter-go:v9 + docker run --rm --platform linux/amd64 -v '$(CURDIR):/tmp/lint:rw' oxsecurity/megalinter-go:v9 + # Show help help: @echo "Available targets:" @@ -124,4 +129,5 @@ help: @echo " install-tools - Install development tools" @echo " docker-build - Build Docker image" @echo " ko-build - Build ko image locally with :local tag" + @echo " megalinter - Run MegaLinter locally via Docker" @echo " help - Show this help message" diff --git a/README.md b/README.md index 01f3775..f0b1af6 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server that c The LFX MCP Server is available as a hosted, production service at: -``` +```text https://mcp.lfx.dev/mcp ``` @@ -119,71 +119,71 @@ Before hitting **Connect**, follow the **Open Auth Settings** button, then selec ### Projects -| Tool | Description | -|------|-------------| +| Tool | Description | +|-------------------|---------------------------------------------------------------| | `search_projects` | Search for LFX projects by name with typeahead and pagination | -| `get_project` | Get a project's base info and settings by UID | +| `get_project` | Get a project's base info and settings by UID | ### Committees -| Tool | Description | -|------|-------------| -| `search_committees` | Search for committees by name; optionally filter by project | -| `get_committee` | Get a committee's base info and settings by UID | -| `create_committee` | Create a new committee under a project | -| `update_committee` | Update a committee's base information | +| Tool | Description | +|-----------------------------|-------------------------------------------------------------------------------------------| +| `search_committees` | Search for committees by name; optionally filter by project | +| `get_committee` | Get a committee's base info and settings by UID | +| `create_committee` | Create a new committee under a project | +| `update_committee` | Update a committee's base information | | `update_committee_settings` | Update a committee's settings (visibility, email requirements, meeting attendee defaults) | -| `delete_committee` | Delete a committee by UID | -| `search_committee_members` | Search committee members; filter by committee, project, or name | -| `get_committee_member` | Get a specific committee member by committee and member UID | -| `create_committee_member` | Add a new member to a committee | -| `update_committee_member` | Update an existing committee member's information | -| `delete_committee_member` | Remove a member from a committee | +| `delete_committee` | Delete a committee by UID | +| `search_committee_members` | Search committee members; filter by committee, project, or name | +| `get_committee_member` | Get a specific committee member by committee and member UID | +| `create_committee_member` | Add a new member to a committee | +| `update_committee_member` | Update an existing committee member's information | +| `delete_committee_member` | Remove a member from a committee | ### Mailing Lists -| Tool | Description | -|------|-------------| -| `search_mailing_lists` | Search for mailing lists by name; optionally filter by project | -| `get_mailing_list` | Get a mailing list's base info and settings by UID | -| `get_mailing_list_service` | Get a mailing list service's base info and settings by UID | -| `search_mailing_list_members` | Search mailing list members; filter by list, project, or name | -| `get_mailing_list_member` | Get a specific mailing list member by list and member UID | +| Tool | Description | +|-------------------------------|----------------------------------------------------------------| +| `search_mailing_lists` | Search for mailing lists by name; optionally filter by project | +| `get_mailing_list` | Get a mailing list's base info and settings by UID | +| `get_mailing_list_service` | Get a mailing list service's base info and settings by UID | +| `search_mailing_list_members` | Search mailing list members; filter by list, project, or name | +| `get_mailing_list_member` | Get a specific mailing list member by list and member UID | ### Members -| Tool | Description | -|------|-------------| -| `search_members` | Search and filter members (memberships) by status, tier, organization, and more | -| `get_member_membership` | Get a single member's membership details by member and membership ID | -| `get_membership_key_contacts` | Get key contacts (primary contacts, board members) for a membership | +| Tool | Description | +|-------------------------------|---------------------------------------------------------------------------------| +| `search_members` | Search and filter members (memberships) by status, tier, organization, and more | +| `get_member_membership` | Get a single member's membership details by member and membership ID | +| `get_membership_key_contacts` | Get key contacts (primary contacts, board members) for a membership | ### Meetings -| Tool | Description | -|------|-------------| -| `search_meetings` | Search for meetings; filter by project, committee, date range | -| `get_meeting` | Get a meeting by UID | +| Tool | Description | +|------------------------------|-------------------------------------------------------------------| +| `search_meetings` | Search for meetings; filter by project, committee, date range | +| `get_meeting` | Get a meeting by UID | | `search_meeting_registrants` | Search meeting registrants; filter by meeting, committee, project | -| `get_meeting_registrant` | Get a meeting registrant by UID | +| `get_meeting_registrant` | Get a meeting registrant by UID | ### Past Meeting Data -| Tool | Description | -|------|-------------| +| Tool | Description | +|------------------------------------|-------------------------------------------------------------------------| | `search_past_meeting_participants` | Search past meeting participants; filter by meeting, committee, project | -| `get_past_meeting_participant` | Get a past meeting participant by UID | -| `search_past_meeting_transcripts` | Search past meeting transcripts; filter by meeting, committee, project | -| `get_past_meeting_transcript` | Get a past meeting transcript by UID | -| `search_past_meeting_summaries` | Search past meeting summaries; filter by meeting, committee, project | -| `get_past_meeting_summary` | Get a past meeting summary by UID | +| `get_past_meeting_participant` | Get a past meeting participant by UID | +| `search_past_meeting_transcripts` | Search past meeting transcripts; filter by meeting, committee, project | +| `get_past_meeting_transcript` | Get a past meeting transcript by UID | +| `search_past_meeting_summaries` | Search past meeting summaries; filter by meeting, committee, project | +| `get_past_meeting_summary` | Get a past meeting summary by UID | ### Utility -| Tool | Description | -|------|-------------| -| `hello_world` | Simple greeting tool for testing MCP connectivity | -| `user_info` | Get the authenticated user's OpenID Connect profile | +| Tool | Description | +|---------------|-----------------------------------------------------| +| `hello_world` | Simple greeting tool for testing MCP connectivity | +| `user_info` | Get the authenticated user's OpenID Connect profile | ## License diff --git a/docs/service-api-architecture.md b/docs/service-api-architecture.md index 55df324..3ba99e2 100644 --- a/docs/service-api-architecture.md +++ b/docs/service-api-architecture.md @@ -13,10 +13,10 @@ The MCP server acts as the authorization gateway: before proxying a request to a Service tools enforce two layers of authorization: **MCP scopes** (checked at dispatch) and **V2 relations** (checked inside the handler via OpenFGA). Both must pass. -| Service | MCP Scope | Required V2 Relation | Additional | Rationale | -|---------|-----------|---------------------|------------|-----------| -| **Member Onboarding** | `manage:all` | `writer` | β€” | Managing onboarding workflows is a write-level project operation | -| **LFX Lens** | `read:all` | `auditor` | `lf_staff` claim | Analytics/reporting requires auditor-level read access; staff-only | +| Service | MCP Scope | Required V2 Relation | Additional | Rationale | +|-----------------------|--------------|----------------------|------------------|--------------------------------------------------------------------| +| **Member Onboarding** | `manage:all` | `writer` | β€” | Managing onboarding workflows is a write-level project operation | +| **LFX Lens** | `read:all` | `auditor` | `lf_staff` claim | Analytics/reporting requires auditor-level read access; staff-only | MCP scopes act as an upper bound on what a token can do β€” like a reduced-scope PAT in GitHub. Even if a user has `writer` access to a project in OpenFGA, a `read:all`-only MCP token cannot call onboarding tools. @@ -26,11 +26,11 @@ MCP scopes act as an upper bound on what a token can do β€” like a reduced-scope All three tokens are issued by Auth0. The MCP server's M2M client (`LFX MCP Server`) authenticates to Auth0 for both the token exchange and client credentials grants β€” same client, different grant types. -| Token | Type | Purpose | Grant type | User identity? | -|-------|------|---------|-----------|----------------| -| MCP JWT | User | Authenticates the human user | Authorization code (OAuth login) | Yes | -| V2 Token | User-delegated | Slug resolution + access-check | Token exchange (RFC 8693): M2M client credentials + user's MCP JWT | Yes (delegated) | -| Service Token | Machine (M2M) | Authenticates MCP server to Lens/Onboarding | Client credentials: M2M client credentials only | No β€” pure machine | +| Token | Type | Purpose | Grant type | User identity? | +|---------------|----------------|---------------------------------------------|--------------------------------------------------------------------|-------------------| +| MCP JWT | User | Authenticates the human user | Authorization code (OAuth login) | Yes | +| V2 Token | User-delegated | Slug resolution + access-check | Token exchange (RFC 8693): M2M client credentials + user's MCP JWT | Yes (delegated) | +| Service Token | Machine (M2M) | Authenticates MCP server to Lens/Onboarding | Client credentials: M2M client credentials only | No β€” pure machine | --- @@ -149,19 +149,19 @@ Results are cached in-memory (slugβ†’UUID mappings are stable). All tools require the `lf_staff` JWT claim and `auditor` relation to the project. -| MCP Tool | Backend Endpoint | Method | Description | -|----------|-----------------|--------|-------------| +| MCP Tool | Backend Endpoint | Method | Description | +|------------------|-----------------------------------------|--------|---------------------------------------------------------------------------------------------------------------| | `lfx_lens_query` | `/workflows/lfx-lens-mcp-workflow/runs` | `POST` | Query LFX Lens analytics for a project. Accepts a natural language question and returns an analytics summary. | #### LFX Lens API `POST /workflows/lfx-lens-mcp-workflow/runs` accepts `multipart/form-data`: -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `message` | string | Yes | The user's natural language question | -| `additional_data` | JSON string | Yes | `{"foundation": {"slug": ""}}` | -| `stream` | string | No | `"false"` (default) or `"true"` | +| Field | Type | Required | Description | +|-------------------|-------------|----------|-------------------------------------------------| +| `message` | string | Yes | The user's natural language question | +| `additional_data` | JSON string | Yes | `{"foundation": {"slug": ""}}` | +| `stream` | string | No | `"false"` (default) or `"true"` | Response: ```json @@ -181,33 +181,33 @@ The onboarding service exposes two sets of endpoints: - **Custom REST endpoints** under `/member-onboarding/` β€” for memberships and agent configs. - **AgentOS framework endpoints** under `/agents/{agent_id}/runs` β€” for running AI agents. -| MCP Tool | Backend Endpoint | Method | Description | -|----------|-----------------|--------|-------------| -| `onboarding_list_memberships` | `/member-onboarding/{slug}/memberships` | `GET` | List memberships for a project with per-agent action/todo counts. Accepts `status` filter (`all`, `pending`, `in_progress`, `closed`). | -| `onboarding_get_membership` | `/member-onboarding/{slug}/memberships/{id}` | `GET` | Get a single membership with full agent details. | -| `onboarding_run_agent` | `/agents/{agent_id}/runs` | `POST` | Run a specific onboarding agent for a membership. Supports a `preview` flag to dry-run the agent without executing side effects. | +| MCP Tool | Backend Endpoint | Method | Description | +|-------------------------------|----------------------------------------------|--------|----------------------------------------------------------------------------------------------------------------------------------------| +| `onboarding_list_memberships` | `/member-onboarding/{slug}/memberships` | `GET` | List memberships for a project with per-agent action/todo counts. Accepts `status` filter (`all`, `pending`, `in_progress`, `closed`). | +| `onboarding_get_membership` | `/member-onboarding/{slug}/memberships/{id}` | `GET` | Get a single membership with full agent details. | +| `onboarding_run_agent` | `/agents/{agent_id}/runs` | `POST` | Run a specific onboarding agent for a membership. Supports a `preview` flag to dry-run the agent without executing side effects. | #### Agent IDs -| Agent ID | Description | -|----------|-------------| -| `member-onboarding-slack` | Adds members to Slack channels | -| `member-onboarding-email` | Sends onboarding emails based on templates | -| `member-onboarding-discord` | Assigns Discord roles to members | -| `member-onboarding-github` | Creates PRs / file changes in GitHub repos | -| `member-onboarding-committees` | Adds members to LFX committees | -| `member-onboarding-hubspot-workflow` | Enrolls contacts in HubSpot workflows | +| Agent ID | Description | +|--------------------------------------|--------------------------------------------| +| `member-onboarding-slack` | Adds members to Slack channels | +| `member-onboarding-email` | Sends onboarding emails based on templates | +| `member-onboarding-discord` | Assigns Discord roles to members | +| `member-onboarding-github` | Creates PRs / file changes in GitHub repos | +| `member-onboarding-committees` | Adds members to LFX committees | +| `member-onboarding-hubspot-workflow` | Enrolls contacts in HubSpot workflows | #### AgentOS Run Endpoint `POST /agents/{agent_id}/runs` accepts `multipart/form-data`: -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| `message` | string | Yes | Text input describing what the agent should do | -| `stream` | boolean | No | Enable streaming via Server-Sent Events | -| `session_id` | string | No | Session ID for context continuity | -| `user_id` | string | No | User context identifier | +| Field | Type | Required | Description | +|--------------|---------|----------|------------------------------------------------| +| `message` | string | Yes | Text input describing what the agent should do | +| `stream` | boolean | No | Enable streaming via Server-Sent Events | +| `session_id` | string | No | Session ID for context continuity | +| `user_id` | string | No | User context identifier | The `preview` flag is handled at the MCP tool level by routing to the `member-onboarding-preview` agent ID instead of the requested agent. This returns predicted actions and prerequisite status without executing side effects. @@ -226,12 +226,12 @@ Verbs are consistent with the V2 tools: `search`, `get`, `list`, `create`, `upda ## Auth0 Resource Servers -| Resource Server | Audience | Used By | Scopes | -|----------------|----------|---------|--------| -| LFX MCP API | `mcp.lfx.dev/mcp` | Claude, Cursor, Inspector | `read:all`, `manage:all` | -| LFX V2 API | `lfx-api.v2.cluster.lfx.dev/` | MCP server (token exchange) | `access:api` | -| LFX Lens API | configured via `LFXMCP_LENS_API_AUDIENCE` | MCP server (client credentials) | `access:api` | -| Member Onboarding API | configured via `LFXMCP_ONBOARDING_API_AUDIENCE` | MCP server (client credentials) | `access:api` | +| Resource Server | Audience | Used By | Scopes | +|-----------------------|-------------------------------------------------|---------------------------------|--------------------------| +| LFX MCP API | `mcp.lfx.dev/mcp` | Claude, Cursor, Inspector | `read:all`, `manage:all` | +| LFX V2 API | `lfx-api.v2.cluster.lfx.dev/` | MCP server (token exchange) | `access:api` | +| LFX Lens API | configured via `LFXMCP_LENS_API_AUDIENCE` | MCP server (client credentials) | `access:api` | +| Member Onboarding API | configured via `LFXMCP_ONBOARDING_API_AUDIENCE` | MCP server (client credentials) | `access:api` | --- @@ -239,12 +239,12 @@ Verbs are consistent with the V2 tools: `search`, `get`, `list`, `create`, `upda ### Environment Variables -| Variable | Description | -|----------|-------------| -| `LFXMCP_ONBOARDING_API_URL` | Base URL of the member onboarding service | +| Variable | Description | +|----------------------------------|--------------------------------------------------------------| +| `LFXMCP_ONBOARDING_API_URL` | Base URL of the member onboarding service | | `LFXMCP_ONBOARDING_API_AUDIENCE` | Auth0 resource server audience for the member onboarding API | -| `LFXMCP_LENS_API_URL` | Base URL of the LFX Lens service | -| `LFXMCP_LENS_API_AUDIENCE` | Auth0 resource server audience for the LFX Lens API | +| `LFXMCP_LENS_API_URL` | Base URL of the LFX Lens service | +| `LFXMCP_LENS_API_AUDIENCE` | Auth0 resource server audience for the LFX Lens API | Existing M2M credentials (`LFXMCP_CLIENT_ID`, `LFXMCP_CLIENT_SECRET` or `LFXMCP_CLIENT_ASSERTION_SIGNING_KEY`, `LFXMCP_TOKEN_ENDPOINT`) are reused for both token exchange (V2 access-check) and client credentials grants (service API authentication). diff --git a/go.mod b/go.mod index c8ef8c9..cf32e2f 100644 --- a/go.mod +++ b/go.mod @@ -2,34 +2,35 @@ // SPDX-License-Identifier: MIT module github.com/linuxfoundation/lfx-mcp -go 1.26.1 +go 1.26.2 require ( github.com/knadh/koanf/providers/basicflag v1.1.0 github.com/knadh/koanf/providers/env/v2 v2.0.0 github.com/knadh/koanf/v2 v2.3.4 github.com/lestrrat-go/jwx/v2 v2.1.6 - github.com/linuxfoundation/lfx-v2-committee-service v0.2.27 - github.com/linuxfoundation/lfx-v2-mailing-list-service v0.3.0 - github.com/linuxfoundation/lfx-v2-member-service v0.5.0 - github.com/linuxfoundation/lfx-v2-project-service v0.6.0 - github.com/linuxfoundation/lfx-v2-query-service v0.4.13 + github.com/linuxfoundation/lfx-v2-committee-service v0.2.29 + github.com/linuxfoundation/lfx-v2-mailing-list-service v0.4.7 + github.com/linuxfoundation/lfx-v2-member-service v0.5.1 + github.com/linuxfoundation/lfx-v2-project-service v0.6.1 + github.com/linuxfoundation/lfx-v2-query-service v0.4.14 github.com/modelcontextprotocol/go-sdk v1.5.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/remychantenay/slog-otel v1.3.5 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 - go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 - go.opentelemetry.io/otel v1.42.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 - go.opentelemetry.io/otel/log v0.18.0 - go.opentelemetry.io/otel/sdk v1.42.0 - go.opentelemetry.io/otel/sdk/log v0.18.0 - go.opentelemetry.io/otel/sdk/metric v1.42.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 + go.opentelemetry.io/contrib/propagators/jaeger v1.43.0 + go.opentelemetry.io/otel v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 + go.opentelemetry.io/otel/log v0.19.0 + go.opentelemetry.io/otel/sdk v1.43.0 + go.opentelemetry.io/otel/sdk/log v0.19.0 + go.opentelemetry.io/otel/sdk/metric v1.43.0 + go.opentelemetry.io/otel/trace v1.43.0 goa.design/goa/v3 v3.25.3 ) @@ -59,17 +60,16 @@ require ( github.com/segmentio/encoding v0.5.4 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 // indirect - go.opentelemetry.io/otel/metric v1.42.0 // indirect - go.opentelemetry.io/otel/trace v1.42.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.10.0 // indirect - golang.org/x/crypto v0.49.0 // indirect - golang.org/x/net v0.52.0 // indirect + golang.org/x/crypto v0.50.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.35.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect google.golang.org/grpc v1.80.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/go.sum b/go.sum index 447d705..c816648 100644 --- a/go.sum +++ b/go.sum @@ -54,16 +54,16 @@ github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVf github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/linuxfoundation/lfx-v2-committee-service v0.2.27 h1:n6p3o0yxBYeQGnOg51drX/tbBkiRGmbTAcb5qpeWIPk= -github.com/linuxfoundation/lfx-v2-committee-service v0.2.27/go.mod h1:Ew8lSv3US9q4d9nO+QFa7YaEb18D+LYyKwjAW+h9CQc= -github.com/linuxfoundation/lfx-v2-mailing-list-service v0.3.0 h1:CS6zl1ZqL7GTaj3VxOwTSonK9thJQS1ea0XTru1XEBI= -github.com/linuxfoundation/lfx-v2-mailing-list-service v0.3.0/go.mod h1:2vY8kKHA8EJi/MPTAN3fQPqX+s2Il/VSgVs4w4HmpEI= -github.com/linuxfoundation/lfx-v2-member-service v0.5.0 h1:1LoxJfs/QsJddaiw0k/1/339owFfpeaWhsyf3iQP/tE= -github.com/linuxfoundation/lfx-v2-member-service v0.5.0/go.mod h1:qMzCkX1KayvzmxZQhVL2+dX1jXHqU3CcRGZ/t41vbgs= -github.com/linuxfoundation/lfx-v2-project-service v0.6.0 h1:fwiRzgwh/d4oTOtbUxawuTsLtpJdj0Mpa9ln59Kmt+A= -github.com/linuxfoundation/lfx-v2-project-service v0.6.0/go.mod h1:pwUWh2KKmV8Ji+wCSrlidvZC7VOn0gVCfSFv7Cyoe5o= -github.com/linuxfoundation/lfx-v2-query-service v0.4.13 h1:jD2vScUdqihm+QZokTe6Ae5XqAJ5oRsSMK4Of2gmOyc= -github.com/linuxfoundation/lfx-v2-query-service v0.4.13/go.mod h1:XsMmtsCazX4JvDsCU9AhrUmtQ/mej2AdmBDeTpN7364= +github.com/linuxfoundation/lfx-v2-committee-service v0.2.29 h1:yWFendpoTkDQb4f8138PTlBWM6WvA49xDo8XSH1ACFw= +github.com/linuxfoundation/lfx-v2-committee-service v0.2.29/go.mod h1:Ew8lSv3US9q4d9nO+QFa7YaEb18D+LYyKwjAW+h9CQc= +github.com/linuxfoundation/lfx-v2-mailing-list-service v0.4.7 h1:TBr31k7lk2BWOAAsO+CHy3kqa+J2Avhfl3THtMm5Hak= +github.com/linuxfoundation/lfx-v2-mailing-list-service v0.4.7/go.mod h1:wi+8mGAKx5nNrpERLiT0kCIrxnPcLdGgR8s0zW04vso= +github.com/linuxfoundation/lfx-v2-member-service v0.5.1 h1:IqZ0eKS4ySKDAKW31iIHFgJfGb7bInUvN0Ovx3xoFMU= +github.com/linuxfoundation/lfx-v2-member-service v0.5.1/go.mod h1:qMzCkX1KayvzmxZQhVL2+dX1jXHqU3CcRGZ/t41vbgs= +github.com/linuxfoundation/lfx-v2-project-service v0.6.1 h1:Cv2a0qGFYriC3Rwjo3cXwD92kZrFRCjQzLGwi+Wq9cg= +github.com/linuxfoundation/lfx-v2-project-service v0.6.1/go.mod h1:pwUWh2KKmV8Ji+wCSrlidvZC7VOn0gVCfSFv7Cyoe5o= +github.com/linuxfoundation/lfx-v2-query-service v0.4.14 h1:iwkrTT6kG5iFnk/lmEtcRBFIXdhUOr4niNF3nci0SBw= +github.com/linuxfoundation/lfx-v2-query-service v0.4.14/go.mod h1:3uHRcBPfibwW3Y95s1ZCc5XHxw8B7nK7nGLPtnAyqUo= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -90,64 +90,64 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= -go.opentelemetry.io/contrib/propagators/jaeger v1.42.0 h1:jP8unWI6q5kcb3gpGLjKDGaUa+JW+nHKWvpS/q+YuWA= -go.opentelemetry.io/contrib/propagators/jaeger v1.42.0/go.mod h1:xd89e/pUyPatUP1C4z1UknD9jHptESO99tWyvd4mWD4= -go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= -go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0 h1:deI9UQMoGFgrg5iLPgzueqFPHevDl+28YKfSpPTI6rY= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.18.0/go.mod h1:PFx9NgpNUKXdf7J4Q3agRxMs3Y07QhTCVipKmLsMKnU= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0 h1:icqq3Z34UrEFk2u+HMhTtRsvo7Ues+eiJVjaJt62njs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.18.0/go.mod h1:W2m8P+d5Wn5kipj4/xmbt9uMqezEKfBjzVJadfABSBE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0 h1:MdKucPl/HbzckWWEisiNqMPhRrAOQX8r4jTuGr636gk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.42.0/go.mod h1:RolT8tWtfHcjajEH5wFIZ4Dgh5jpPdFXYV9pTAk/qjc= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0 h1:H7O6RlGOMTizyl3R08Kn5pdM06bnH8oscSj7o11tmLA= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.42.0/go.mod h1:mBFWu/WOVDkWWsR7Tx7h6EpQB8wsv7P0Yrh0Pb7othc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0 h1:THuZiwpQZuHPul65w4WcwEnkX2QIuMT+UFoOrygtoJw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.42.0/go.mod h1:J2pvYM5NGHofZ2/Ru6zw/TNWnEQp5crgyDeSrYpXkAw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0 h1:zWWrB1U6nqhS/k6zYB74CjRpuiitRtLLi68VcgmOEto= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.42.0/go.mod h1:2qXPNBX1OVRC0IwOnfo1ljoid+RD0QK3443EaqVlsOU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0 h1:uLXP+3mghfMf7XmV4PkGfFhFKuNWoCvvx5wP/wOXo0o= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.42.0/go.mod h1:v0Tj04armyT59mnURNUJf7RCKcKzq+lgJs6QSjHjaTc= -go.opentelemetry.io/otel/log v0.18.0 h1:XgeQIIBjZZrliksMEbcwMZefoOSMI1hdjiLEiiB0bAg= -go.opentelemetry.io/otel/log v0.18.0/go.mod h1:KEV1kad0NofR3ycsiDH4Yjcoj0+8206I6Ox2QYFSNgI= -go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= -go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= -go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= -go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= -go.opentelemetry.io/otel/sdk/log v0.18.0 h1:n8OyZr7t7otkeTnPTbDNom6rW16TBYGtvyy2Gk6buQw= -go.opentelemetry.io/otel/sdk/log v0.18.0/go.mod h1:C0+wxkTwKpOCZLrlJ3pewPiiQwpzycPI/u6W0Z9fuYk= -go.opentelemetry.io/otel/sdk/log/logtest v0.18.0 h1:l3mYuPsuBx6UKE47BVcPrZoZ0q/KER57vbj2qkgDLXA= -go.opentelemetry.io/otel/sdk/log/logtest v0.18.0/go.mod h1:7cHtiVJpZebB3wybTa4NG+FUo5NPe3PROz1FqB0+qdw= -go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= -go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= -go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= -go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= +go.opentelemetry.io/contrib/propagators/jaeger v1.43.0 h1:peiLMz1+aqJE+3L4mOVtR9wlmv+yh/JVYXCBjqmzJJE= +go.opentelemetry.io/contrib/propagators/jaeger v1.43.0/go.mod h1:Agvif+4A8p/3UtZzJ0MCcDEuQwgtrzM71DueU41DCs8= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= +go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= +go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= +go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= +go.opentelemetry.io/otel/sdk/log/logtest v0.19.0 h1:BEbF7ZBB6qQloV/Ub1+3NQoOUnVtcGkU3XX4Ws3GQfk= +go.opentelemetry.io/otel/sdk/log/logtest v0.19.0/go.mod h1:Lua81/3yM0wOmoHTokLj9y9ADeA02v1naRrVrkAZuKk= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= goa.design/goa/v3 v3.25.3 h1:gnOm2Vu0HMvveKpcqL6aWYQTP2puiwrEJWLQc79/294= goa.design/goa/v3 v3.25.3/go.mod h1:VZ8CcXJRZh09ijtNJJS2gNyKufpmrM+Ul/Qy3viwcOU= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= -google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d h1:/aDRtSZJjyLQzm75d+a1wOJaqyKBMvIAfeQmoa3ORiI= +google.golang.org/genproto/googleapis/api v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:etfGUgejTiadZAUaEP14NP97xi1RGeawqkjDARA/UOs= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= diff --git a/internal/lfxv2/client.go b/internal/lfxv2/client.go index b9c722c..1e63e9c 100644 --- a/internal/lfxv2/client.go +++ b/internal/lfxv2/client.go @@ -223,6 +223,10 @@ func NewClients(_ context.Context, cfg ClientConfig) (*Clients, error) { committeeHTTPClient.ListCommitteeLinkFolders(), committeeHTTPClient.CreateCommitteeLinkFolder(), committeeHTTPClient.DeleteCommitteeLinkFolder(), + committeeHTTPClient.UploadCommitteeDocument(nil), + committeeHTTPClient.GetCommitteeDocument(), + committeeHTTPClient.DownloadCommitteeDocument(), + committeeHTTPClient.DeleteCommitteeDocument(), ) // Initialize mailing list service client. @@ -243,23 +247,29 @@ func NewClients(_ context.Context, cfg ClientConfig) (*Clients, error) { clients.MailingList = mailinglist.NewClient( mlHTTPClient.Livez(), mlHTTPClient.Readyz(), - mlHTTPClient.CreateGrpsioService(), - mlHTTPClient.GetGrpsioService(), - mlHTTPClient.UpdateGrpsioService(), - mlHTTPClient.DeleteGrpsioService(), - mlHTTPClient.GetGrpsioServiceSettings(), - mlHTTPClient.UpdateGrpsioServiceSettings(), - mlHTTPClient.CreateGrpsioMailingList(), - mlHTTPClient.GetGrpsioMailingList(), - mlHTTPClient.UpdateGrpsioMailingList(), - mlHTTPClient.DeleteGrpsioMailingList(), - mlHTTPClient.GetGrpsioMailingListSettings(), - mlHTTPClient.UpdateGrpsioMailingListSettings(), - mlHTTPClient.CreateGrpsioMailingListMember(), - mlHTTPClient.GetGrpsioMailingListMember(), - mlHTTPClient.UpdateGrpsioMailingListMember(), - mlHTTPClient.DeleteGrpsioMailingListMember(), - mlHTTPClient.GroupsioWebhook(), + mlHTTPClient.ListGroupsioServices(), + mlHTTPClient.CreateGroupsioService(), + mlHTTPClient.GetGroupsioService(), + mlHTTPClient.UpdateGroupsioService(), + mlHTTPClient.DeleteGroupsioService(), + mlHTTPClient.GetGroupsioServiceProjects(), + mlHTTPClient.FindParentGroupsioService(), + mlHTTPClient.ListGroupsioMailingLists(), + mlHTTPClient.CreateGroupsioMailingList(), + mlHTTPClient.GetGroupsioMailingList(), + mlHTTPClient.UpdateGroupsioMailingList(), + mlHTTPClient.DeleteGroupsioMailingList(), + mlHTTPClient.GetGroupsioMailingListCount(), + mlHTTPClient.GetGroupsioMailingListMemberCount(), + mlHTTPClient.ListGroupsioMembers(), + mlHTTPClient.AddGroupsioMember(), + mlHTTPClient.GetGroupsioMember(), + mlHTTPClient.UpdateGroupsioMember(), + mlHTTPClient.DeleteGroupsioMember(), + mlHTTPClient.InviteGroupsioMembers(), + mlHTTPClient.CheckGroupsioSubscriber(), + mlHTTPClient.GetGroupsioArtifact(), + mlHTTPClient.GetGroupsioArtifactDownload(), ) // Initialize member service client. diff --git a/internal/tools/mailing_list.go b/internal/tools/mailing_list.go index 3d2bd81..3c1e485 100644 --- a/internal/tools/mailing_list.go +++ b/internal/tools/mailing_list.go @@ -42,22 +42,22 @@ type GetMailingListServiceArgs struct { // GetMailingListArgs defines the input parameters for the get_mailing_list tool. type GetMailingListArgs struct { - UID string `json:"uid" jsonschema:"The UID of the mailing list to retrieve"` + ID string `json:"id" jsonschema:"The Groups.io numeric group ID of the mailing list to retrieve (e.g. 145670)"` } // GetMailingListMemberArgs defines the input parameters for the get_mailing_list_member tool. type GetMailingListMemberArgs struct { - MailingListUID string `json:"mailing_list_uid" jsonschema:"The UID of the mailing list"` - MemberUID string `json:"member_uid" jsonschema:"The UID of the mailing list member"` + MailingListID string `json:"mailing_list_id" jsonschema:"The Groups.io numeric group ID of the mailing list (e.g. 145670)"` + MemberID string `json:"member_id" jsonschema:"The Groups.io numeric member ID (e.g. 14875835)"` } // SearchMailingListMembersArgs defines the input parameters for the search_mailing_list_members tool. type SearchMailingListMembersArgs struct { - MailingListUID string `json:"mailing_list_uid,omitempty" jsonschema:"Optional UID of the mailing list to filter members by"` - ProjectUID string `json:"project_uid,omitempty" jsonschema:"Optional project UID to filter mailing list members by project"` - Name string `json:"name,omitempty" jsonschema:"Name or partial name of the member to search for"` - PageSize int `json:"page_size,omitempty" jsonschema:"Number of results per page (default 10)"` - PageToken string `json:"page_token,omitempty" jsonschema:"Opaque pagination token from a previous search response"` + MailingListID string `json:"mailing_list_id,omitempty" jsonschema:"Optional Groups.io numeric group ID of the mailing list to filter members by (e.g. 145670)"` + ProjectUID string `json:"project_uid,omitempty" jsonschema:"Optional project UID to filter mailing list members by project"` + Name string `json:"name,omitempty" jsonschema:"Name or partial name of the member to search for"` + PageSize int `json:"page_size,omitempty" jsonschema:"Number of results per page (default 10)"` + PageToken string `json:"page_token,omitempty" jsonschema:"Opaque pagination token from a previous search response"` } // SearchMailingListsArgs defines the input parameters for the search_mailing_lists tool. @@ -84,7 +84,7 @@ func RegisterGetMailingListService(server *mcp.Server) { func RegisterGetMailingList(server *mcp.Server) { AddToolWithScopes(server, &mcp.Tool{ Name: "get_mailing_list", - Description: "Get a mailing list's base info and settings by its UID. Privileged settings may be omitted if the caller lacks sufficient permissions.", + Description: "Get a mailing list by its Groups.io numeric group ID (e.g. 145670).", Annotations: &mcp.ToolAnnotations{ Title: "Get Mailing List", ReadOnlyHint: true, @@ -96,7 +96,7 @@ func RegisterGetMailingList(server *mcp.Server) { func RegisterGetMailingListMember(server *mcp.Server) { AddToolWithScopes(server, &mcp.Tool{ Name: "get_mailing_list_member", - Description: "Get a specific mailing list member by mailing list UID and member UID.", + Description: "Get a specific mailing list member by Groups.io mailing list ID and member ID.", Annotations: &mcp.ToolAnnotations{ Title: "Get Mailing List Member", ReadOnlyHint: true, @@ -108,7 +108,7 @@ func RegisterGetMailingListMember(server *mcp.Server) { func RegisterSearchMailingListMembers(server *mcp.Server) { AddToolWithScopes(server, &mcp.Tool{ Name: "search_mailing_list_members", - Description: "Search for LFX mailing list members. Optionally filter by mailing list UID, project UID, and/or name. At least one filter is recommended but not required.", + Description: "Search for LFX mailing list members. Optionally filter by Groups.io mailing list ID, project UID, and/or name. At least one filter is recommended but not required.", Annotations: &mcp.ToolAnnotations{ Title: "Search Mailing List Members", ReadOnlyHint: true, @@ -167,11 +167,11 @@ func handleGetMailingListService(ctx context.Context, req *mcp.CallToolRequest, logger.InfoContext(ctx, "fetching mailing list service", "uid", args.UID) - baseResult, err := clients.MailingList.GetGrpsioService(ctx, &mailinglist.GetGrpsioServicePayload{ - UID: &args.UID, + baseResult, err := clients.MailingList.GetGroupsioService(ctx, &mailinglist.GetGroupsioServicePayload{ + ServiceID: args.UID, }) if err != nil { - logger.ErrorContext(ctx, "GetGrpsioService failed", "error", err, "uid", args.UID) + logger.ErrorContext(ctx, "GetGroupsioService failed", "error", err, "uid", args.UID) return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: friendlyAPIError("failed to get mailing list service", err)}, @@ -180,29 +180,7 @@ func handleGetMailingListService(ctx context.Context, req *mcp.CallToolRequest, }, nil, nil } - var serviceSettings *mailinglist.GrpsIoServiceSettings - settingsResult, err := clients.MailingList.GetGrpsioServiceSettings(ctx, &mailinglist.GetGrpsioServiceSettingsPayload{ - UID: &args.UID, - }) - var settingsWarning string - if err != nil { - settingsWarning = fmt.Sprintf("WARNING: mailing list service settings unavailable - %s", err.Error()) - logger.ErrorContext(ctx, "getting mailing list service settings failed, returning base only", "error", err, "uid", args.UID) - } else { - serviceSettings = settingsResult.ServiceSettings - } - - type serviceResult struct { - Base *mailinglist.GrpsIoServiceWithReadonlyAttributes `json:"base"` - Settings *mailinglist.GrpsIoServiceSettings `json:"settings,omitempty"` - } - - out := serviceResult{ - Base: baseResult.Service, - Settings: serviceSettings, - } - - prettyJSON, err := json.MarshalIndent(out, "", " ") + prettyJSON, err := json.MarshalIndent(baseResult, "", " ") if err != nil { logger.ErrorContext(ctx, "failed to marshal mailing list service result", "error", err) return &mcp.CallToolResult{ @@ -215,14 +193,10 @@ func handleGetMailingListService(ctx context.Context, req *mcp.CallToolRequest, logger.InfoContext(ctx, "get_mailing_list_service succeeded", "uid", args.UID) - content := []mcp.Content{} - if settingsWarning != "" { - content = append(content, &mcp.TextContent{Text: settingsWarning}) - } - content = append(content, &mcp.TextContent{Text: string(prettyJSON)}) - return &mcp.CallToolResult{ - Content: content, + Content: []mcp.Content{ + &mcp.TextContent{Text: string(prettyJSON)}, + }, }, nil, nil } @@ -240,10 +214,10 @@ func handleGetMailingList(ctx context.Context, req *mcp.CallToolRequest, args Ge }, nil, nil } - if args.UID == "" { + if args.ID == "" { return &mcp.CallToolResult{ Content: []mcp.Content{ - &mcp.TextContent{Text: "Error: uid is required"}, + &mcp.TextContent{Text: "Error: id is required"}, }, IsError: true, }, nil, nil @@ -263,14 +237,13 @@ func handleGetMailingList(ctx context.Context, req *mcp.CallToolRequest, args Ge ctx = mailingListConfig.Clients.WithMCPToken(ctx, mcpToken) clients := mailingListConfig.Clients - logger.InfoContext(ctx, "fetching mailing list", "uid", args.UID) + logger.InfoContext(ctx, "fetching mailing list", "id", args.ID) - baseResult, err := clients.MailingList.GetGrpsioMailingList(ctx, &mailinglist.GetGrpsioMailingListPayload{ - Version: "1", - UID: &args.UID, + baseResult, err := clients.MailingList.GetGroupsioMailingList(ctx, &mailinglist.GetGroupsioMailingListPayload{ + SubgroupID: args.ID, }) if err != nil { - logger.ErrorContext(ctx, "GetGrpsioMailingList failed", "error", err, "uid", args.UID) + logger.ErrorContext(ctx, "GetGroupsioMailingList failed", "error", err, "id", args.ID) return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: friendlyAPIError("failed to get mailing list", err)}, @@ -279,29 +252,7 @@ func handleGetMailingList(ctx context.Context, req *mcp.CallToolRequest, args Ge }, nil, nil } - var mlSettings *mailinglist.GrpsIoMailingListSettings - settingsResult, err := clients.MailingList.GetGrpsioMailingListSettings(ctx, &mailinglist.GetGrpsioMailingListSettingsPayload{ - UID: &args.UID, - }) - var listSettingsWarning string - if err != nil { - listSettingsWarning = fmt.Sprintf("WARNING: mailing list settings unavailable - %s", err.Error()) - logger.ErrorContext(ctx, "getting mailing list settings failed, returning base only", "error", err, "uid", args.UID) - } else { - mlSettings = settingsResult.MailingListSettings - } - - type mailingListResult struct { - Base *mailinglist.GrpsIoMailingListWithReadonlyAttributes `json:"base"` - Settings *mailinglist.GrpsIoMailingListSettings `json:"settings,omitempty"` - } - - out := mailingListResult{ - Base: baseResult.MailingList, - Settings: mlSettings, - } - - prettyJSON, err := json.MarshalIndent(out, "", " ") + prettyJSON, err := json.MarshalIndent(baseResult, "", " ") if err != nil { logger.ErrorContext(ctx, "failed to marshal mailing list result", "error", err) return &mcp.CallToolResult{ @@ -312,16 +263,12 @@ func handleGetMailingList(ctx context.Context, req *mcp.CallToolRequest, args Ge }, nil, nil } - logger.InfoContext(ctx, "get_mailing_list succeeded", "uid", args.UID) - - content := []mcp.Content{} - if listSettingsWarning != "" { - content = append(content, &mcp.TextContent{Text: listSettingsWarning}) - } - content = append(content, &mcp.TextContent{Text: string(prettyJSON)}) + logger.InfoContext(ctx, "get_mailing_list succeeded", "id", args.ID) return &mcp.CallToolResult{ - Content: content, + Content: []mcp.Content{ + &mcp.TextContent{Text: string(prettyJSON)}, + }, }, nil, nil } @@ -339,19 +286,19 @@ func handleGetMailingListMember(ctx context.Context, req *mcp.CallToolRequest, a }, nil, nil } - if args.MailingListUID == "" { + if args.MailingListID == "" { return &mcp.CallToolResult{ Content: []mcp.Content{ - &mcp.TextContent{Text: "Error: mailing_list_uid is required"}, + &mcp.TextContent{Text: "Error: mailing_list_id is required"}, }, IsError: true, }, nil, nil } - if args.MemberUID == "" { + if args.MemberID == "" { return &mcp.CallToolResult{ Content: []mcp.Content{ - &mcp.TextContent{Text: "Error: member_uid is required"}, + &mcp.TextContent{Text: "Error: member_id is required"}, }, IsError: true, }, nil, nil @@ -371,15 +318,14 @@ func handleGetMailingListMember(ctx context.Context, req *mcp.CallToolRequest, a ctx = mailingListConfig.Clients.WithMCPToken(ctx, mcpToken) clients := mailingListConfig.Clients - logger.InfoContext(ctx, "fetching mailing list member", "mailing_list_uid", args.MailingListUID, "member_uid", args.MemberUID) + logger.InfoContext(ctx, "fetching mailing list member", "mailing_list_id", args.MailingListID, "member_id", args.MemberID) - result, err := clients.MailingList.GetGrpsioMailingListMember(ctx, &mailinglist.GetGrpsioMailingListMemberPayload{ - Version: "1", - UID: args.MailingListUID, - MemberUID: args.MemberUID, + result, err := clients.MailingList.GetGroupsioMember(ctx, &mailinglist.GetGroupsioMemberPayload{ + SubgroupID: args.MailingListID, + MemberID: args.MemberID, }) if err != nil { - logger.ErrorContext(ctx, "GetGrpsioMailingListMember failed", "error", err, "mailing_list_uid", args.MailingListUID, "member_uid", args.MemberUID) + logger.ErrorContext(ctx, "GetGroupsioMember failed", "error", err, "mailing_list_id", args.MailingListID, "member_id", args.MemberID) return &mcp.CallToolResult{ Content: []mcp.Content{ &mcp.TextContent{Text: friendlyAPIError("failed to get mailing list member", err)}, @@ -388,7 +334,7 @@ func handleGetMailingListMember(ctx context.Context, req *mcp.CallToolRequest, a }, nil, nil } - prettyJSON, err := json.MarshalIndent(result.Member, "", " ") + prettyJSON, err := json.MarshalIndent(result, "", " ") if err != nil { logger.ErrorContext(ctx, "failed to marshal mailing list member result", "error", err) return &mcp.CallToolResult{ @@ -399,7 +345,7 @@ func handleGetMailingListMember(ctx context.Context, req *mcp.CallToolRequest, a }, nil, nil } - logger.InfoContext(ctx, "get_mailing_list_member succeeded", "mailing_list_uid", args.MailingListUID, "member_uid", args.MemberUID) + logger.InfoContext(ctx, "get_mailing_list_member succeeded", "mailing_list_id", args.MailingListID, "member_id", args.MemberID) return &mcp.CallToolResult{ Content: []mcp.Content{ @@ -553,8 +499,8 @@ func handleSearchMailingListMembers(ctx context.Context, req *mcp.CallToolReques } var tags []string - if args.MailingListUID != "" { - tags = append(tags, fmt.Sprintf("mailing_list_uid:%s", args.MailingListUID)) + if args.MailingListID != "" { + tags = append(tags, fmt.Sprintf("mailing_list_uid:%s", args.MailingListID)) } if args.ProjectUID != "" { tags = append(tags, fmt.Sprintf("project_uid:%s", args.ProjectUID)) @@ -571,7 +517,7 @@ func handleSearchMailingListMembers(ctx context.Context, req *mcp.CallToolReques payload.PageToken = &args.PageToken } - logger.InfoContext(ctx, "searching mailing list members", "mailing_list_uid", args.MailingListUID, "project_uid", args.ProjectUID, "name", args.Name, "page_size", pageSize) + logger.InfoContext(ctx, "searching mailing list members", "mailing_list_id", args.MailingListID, "project_uid", args.ProjectUID, "name", args.Name, "page_size", pageSize) result, err := clients.QuerySvc.QueryResources(ctx, payload) if err != nil { @@ -610,7 +556,7 @@ func handleSearchMailingListMembers(ctx context.Context, req *mcp.CallToolReques }, nil, nil } - logger.InfoContext(ctx, "search_mailing_list_members succeeded", "mailing_list_uid", args.MailingListUID, "project_uid", args.ProjectUID, "count", len(result.Resources)) + logger.InfoContext(ctx, "search_mailing_list_members succeeded", "mailing_list_id", args.MailingListID, "project_uid", args.ProjectUID, "count", len(result.Resources)) content := []mcp.Content{} if pageWarning != "" { diff --git a/scripts/test_oauth_config.sh b/scripts/test_oauth_config.sh index 77699f3..e35dcfb 100755 --- a/scripts/test_oauth_config.sh +++ b/scripts/test_oauth_config.sh @@ -19,31 +19,31 @@ echo "" WORKSPACE="${1:-dev}" DEBUG_FLAG="" if [[ "$2" == "--debug" || "$2" == "-d" ]]; then - DEBUG_FLAG="-debug" - echo "Running tests with debug logging enabled..." + DEBUG_FLAG="-debug" + echo "Running tests with debug logging enabled..." fi # Set environment based on workspace. case $WORKSPACE in - dev) - AUTH0_DOMAIN="linuxfoundation-dev.auth0.com" - LFX_MCP_API_URL="https://lfx-mcp.dev.v2.cluster.linuxfound.info/mcp" - LFX_V2_API_URL="https://lfx-api.dev.v2.cluster.linuxfound.info" - ;; - staging) - AUTH0_DOMAIN="linuxfoundation-staging.auth0.com" - LFX_MCP_API_URL="https://lfx-mcp.staging.v2.cluster.linuxfound.info/mcp" - LFX_V2_API_URL="https://lfx-api.staging.v2.cluster.linuxfound.info" - ;; - prod) - AUTH0_DOMAIN="sso.linuxfoundation.org" - LFX_MCP_API_URL="https://mcp.lfx.dev/mcp" - LFX_V2_API_URL="https://lfx-api.v2.cluster.lfx.dev" - ;; - *) - echo -e "${RED}Error: Invalid workspace '$WORKSPACE'. Use dev, staging, or prod${NC}" - exit 1 - ;; +dev) + AUTH0_DOMAIN="linuxfoundation-dev.auth0.com" + LFX_MCP_API_URL="https://lfx-mcp.dev.v2.cluster.linuxfound.info/mcp" + LFX_V2_API_URL="https://lfx-api.dev.v2.cluster.linuxfound.info" + ;; +staging) + AUTH0_DOMAIN="linuxfoundation-staging.auth0.com" + LFX_MCP_API_URL="https://lfx-mcp.staging.v2.cluster.linuxfound.info/mcp" + LFX_V2_API_URL="https://lfx-api.staging.v2.cluster.linuxfound.info" + ;; +prod) + AUTH0_DOMAIN="sso.linuxfoundation.org" + LFX_MCP_API_URL="https://mcp.lfx.dev/mcp" + LFX_V2_API_URL="https://lfx-api.v2.cluster.lfx.dev" + ;; +*) + echo -e "${RED}Error: Invalid workspace '$WORKSPACE'. Use dev, staging, or prod${NC}" + exit 1 + ;; esac TOKEN_ENDPOINT="https://$AUTH0_DOMAIN/oauth/token" @@ -58,9 +58,9 @@ echo "" # Build the server if needed. if [ ! -f "./bin/lfx-mcp-server" ]; then - echo -e "${YELLOW}Building server...${NC}" - make build - echo "" + echo -e "${YELLOW}Building server...${NC}" + make build + echo "" fi # Test 1: Check Protected Resource Metadata endpoint. @@ -69,11 +69,11 @@ echo "Starting server with OAuth configuration..." # Start server in background with HTTP mode. LFXMCP_MODE=http \ -LFXMCP_HTTP_PORT=8081 \ -LFXMCP_MCP_API_AUTH_SERVERS="https://$AUTH0_DOMAIN" \ -LFXMCP_MCP_API_PUBLIC_URL="$LFX_MCP_API_URL" \ -LFXMCP_TOOLS=hello_world \ -./bin/lfx-mcp-server $DEBUG_FLAG & + LFXMCP_HTTP_PORT=8081 \ + LFXMCP_MCP_API_AUTH_SERVERS="https://$AUTH0_DOMAIN" \ + LFXMCP_MCP_API_PUBLIC_URL="$LFX_MCP_API_URL" \ + LFXMCP_TOOLS=hello_world \ + ./bin/lfx-mcp-server $DEBUG_FLAG & SERVER_PID=$! # Wait for server to start. @@ -95,15 +95,15 @@ SCOPES=$(echo "$PRM_RESPONSE" | jq -r '.scopes_supported[]' | tr '\n' ', ' | sed echo -e "${BLUE}Validation:${NC}" if [ "$RESOURCE" == "$LFX_MCP_API_URL" ]; then - echo -e "${GREEN}βœ“ Resource identifier: $RESOURCE${NC}" + echo -e "${GREEN}βœ“ Resource identifier: $RESOURCE${NC}" else - echo -e "${RED}βœ— Resource identifier mismatch: expected $LFX_MCP_API_URL, got $RESOURCE${NC}" + echo -e "${RED}βœ— Resource identifier mismatch: expected $LFX_MCP_API_URL, got $RESOURCE${NC}" fi if [ "$AUTH_SERVER" == "https://$AUTH0_DOMAIN/" ]; then - echo -e "${GREEN}βœ“ Authorization server: $AUTH_SERVER${NC}" + echo -e "${GREEN}βœ“ Authorization server: $AUTH_SERVER${NC}" else - echo -e "${RED}βœ— Authorization server mismatch: expected https://$AUTH0_DOMAIN/, got $AUTH_SERVER${NC}" + echo -e "${RED}βœ— Authorization server mismatch: expected https://$AUTH0_DOMAIN/, got $AUTH_SERVER${NC}" fi echo -e "${GREEN}βœ“ Bearer methods: $BEARER_METHODS${NC}" @@ -125,8 +125,8 @@ echo "" echo -e "${BLUE}=== Test 3: MCP Endpoint Accessibility ===${NC}" echo "Testing MCP endpoint without authentication..." MCP_RESPONSE=$(curl -s -X POST http://127.0.0.1:8081/mcp \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}') + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}') echo -e "${GREEN}MCP Initialize Response:${NC}" echo "$MCP_RESPONSE" | jq '.' @@ -135,17 +135,17 @@ echo "" # Check if response contains server info. SERVER_NAME=$(echo "$MCP_RESPONSE" | jq -r '.result.serverInfo.name // empty') if [ -n "$SERVER_NAME" ]; then - echo -e "${GREEN}βœ“ MCP server responding: $SERVER_NAME${NC}" + echo -e "${GREEN}βœ“ MCP server responding: $SERVER_NAME${NC}" else - echo -e "${YELLOW}⚠ MCP server may require authentication for initialize${NC}" + echo -e "${YELLOW}⚠ MCP server may require authentication for initialize${NC}" fi echo "" # Test 4: List tools. echo -e "${BLUE}=== Test 4: List Available Tools ===${NC}" TOOLS_RESPONSE=$(curl -s -X POST http://127.0.0.1:8081/mcp \ - -H "Content-Type: application/json" \ - -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}') + -H "Content-Type: application/json" \ + -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}') echo -e "${GREEN}Available Tools:${NC}" echo "$TOOLS_RESPONSE" | jq '.result.tools[]?.name // empty' | tr '\n' ' ' diff --git a/scripts/test_oauth_flow.sh b/scripts/test_oauth_flow.sh index 04645a0..53eea45 100755 --- a/scripts/test_oauth_flow.sh +++ b/scripts/test_oauth_flow.sh @@ -17,19 +17,19 @@ echo "" # Check required environment variables. if [ -z "$LFXMCP_CLIENT_ID" ]; then - echo -e "${RED}Error: LFXMCP_CLIENT_ID is not set${NC}" - exit 1 + echo -e "${RED}Error: LFXMCP_CLIENT_ID is not set${NC}" + exit 1 fi if [ -z "$LFXMCP_CLIENT_ASSERTION_SIGNING_KEY_FILE" ]; then - echo -e "${RED}Error: LFXMCP_CLIENT_ASSERTION_SIGNING_KEY_FILE is not set${NC}" - echo "Please set it to the path of your .pem file" - exit 1 + echo -e "${RED}Error: LFXMCP_CLIENT_ASSERTION_SIGNING_KEY_FILE is not set${NC}" + echo "Please set it to the path of your .pem file" + exit 1 fi if [ ! -f "$LFXMCP_CLIENT_ASSERTION_SIGNING_KEY_FILE" ]; then - echo -e "${RED}Error: Private key file not found: $LFXMCP_CLIENT_ASSERTION_SIGNING_KEY_FILE${NC}" - exit 1 + echo -e "${RED}Error: Private key file not found: $LFXMCP_CLIENT_ASSERTION_SIGNING_KEY_FILE${NC}" + exit 1 fi # Load private key from file. @@ -39,25 +39,25 @@ PRIVATE_KEY=$(cat "$LFXMCP_CLIENT_ASSERTION_SIGNING_KEY_FILE") WORKSPACE="${WORKSPACE:-dev}" case $WORKSPACE in - dev) - AUTH0_DOMAIN="linuxfoundation-dev.auth0.com" - LFX_MCP_API_URL="https://lfx-mcp.dev.v2.cluster.linuxfound.info/mcp" - LFX_V2_API_URL="https://lfx-api.dev.v2.cluster.linuxfound.info" - ;; - staging) - AUTH0_DOMAIN="linuxfoundation-staging.auth0.com" - LFX_MCP_API_URL="https://lfx-mcp.staging.v2.cluster.linuxfound.info/mcp" - LFX_V2_API_URL="https://lfx-api.staging.v2.cluster.linuxfound.info" - ;; - prod) - AUTH0_DOMAIN="sso.linuxfoundation.org" - LFX_MCP_API_URL="https://mcp.lfx.dev/mcp" - LFX_V2_API_URL="https://lfx-api.v2.cluster.lfx.dev" - ;; - *) - echo -e "${RED}Error: Invalid workspace '$WORKSPACE'. Use dev, staging, or prod${NC}" - exit 1 - ;; +dev) + AUTH0_DOMAIN="linuxfoundation-dev.auth0.com" + LFX_MCP_API_URL="https://lfx-mcp.dev.v2.cluster.linuxfound.info/mcp" + LFX_V2_API_URL="https://lfx-api.dev.v2.cluster.linuxfound.info" + ;; +staging) + AUTH0_DOMAIN="linuxfoundation-staging.auth0.com" + LFX_MCP_API_URL="https://lfx-mcp.staging.v2.cluster.linuxfound.info/mcp" + LFX_V2_API_URL="https://lfx-api.staging.v2.cluster.linuxfound.info" + ;; +prod) + AUTH0_DOMAIN="sso.linuxfoundation.org" + LFX_MCP_API_URL="https://mcp.lfx.dev/mcp" + LFX_V2_API_URL="https://lfx-api.v2.cluster.lfx.dev" + ;; +*) + echo -e "${RED}Error: Invalid workspace '$WORKSPACE'. Use dev, staging, or prod${NC}" + exit 1 + ;; esac TOKEN_ENDPOINT="https://$AUTH0_DOMAIN/oauth/token" @@ -74,9 +74,9 @@ echo "" # Build the server if needed. if [ ! -f "./bin/lfx-mcp-server" ]; then - echo -e "${YELLOW}Building server...${NC}" - make build - echo "" + echo -e "${YELLOW}Building server...${NC}" + make build + echo "" fi # Test 1: Check Protected Resource Metadata endpoint. @@ -85,11 +85,11 @@ echo "Starting server with OAuth configuration..." # Start server in background with HTTP mode. LFXMCP_MODE=http \ -LFXMCP_HTTP_PORT=8081 \ -LFXMCP_MCP_API_AUTH_SERVERS="https://$AUTH0_DOMAIN" \ -LFXMCP_MCP_API_PUBLIC_URL="$LFX_MCP_API_URL" \ -LFXMCP_TOOLS=hello_world \ -./bin/lfx-mcp-server & + LFXMCP_HTTP_PORT=8081 \ + LFXMCP_MCP_API_AUTH_SERVERS="https://$AUTH0_DOMAIN" \ + LFXMCP_MCP_API_PUBLIC_URL="$LFX_MCP_API_URL" \ + LFXMCP_TOOLS=hello_world \ + ./bin/lfx-mcp-server & SERVER_PID=$! # Wait for server to start. @@ -107,15 +107,15 @@ RESOURCE=$(echo "$PRM_RESPONSE" | jq -r '.resource') AUTH_SERVER=$(echo "$PRM_RESPONSE" | jq -r '.authorization_servers[0]') if [ "$RESOURCE" == "$LFX_MCP_API_URL" ]; then - echo -e "${GREEN}βœ“ Resource identifier matches expected value${NC}" + echo -e "${GREEN}βœ“ Resource identifier matches expected value${NC}" else - echo -e "${RED}βœ— Resource identifier mismatch: expected $LFX_MCP_API_URL, got $RESOURCE${NC}" + echo -e "${RED}βœ— Resource identifier mismatch: expected $LFX_MCP_API_URL, got $RESOURCE${NC}" fi if [ "$AUTH_SERVER" == "https://$AUTH0_DOMAIN/" ]; then - echo -e "${GREEN}βœ“ Authorization server matches expected value${NC}" + echo -e "${GREEN}βœ“ Authorization server matches expected value${NC}" else - echo -e "${RED}βœ— Authorization server mismatch: expected https://$AUTH0_DOMAIN/, got $AUTH_SERVER${NC}" + echo -e "${RED}βœ— Authorization server mismatch: expected https://$AUTH0_DOMAIN/, got $AUTH_SERVER${NC}" fi # Stop server. @@ -129,91 +129,91 @@ echo -e "${BLUE}=== Test 2: Token Exchange with user_info Tool ===${NC}" # Get a test token from Auth0 (client credentials flow for testing). echo "Getting test MCP token from Auth0..." MCP_TOKEN_RESPONSE=$(curl -s --request POST \ - --url "https://$AUTH0_DOMAIN/oauth/token" \ - --header 'content-type: application/x-www-form-urlencoded' \ - --data "grant_type=client_credentials" \ - --data "client_id=$LFXMCP_CLIENT_ID" \ - --data "client_secret=" \ - --data "audience=$LFX_MCP_API_URL") + --url "https://$AUTH0_DOMAIN/oauth/token" \ + --header 'content-type: application/x-www-form-urlencoded' \ + --data "grant_type=client_credentials" \ + --data "client_id=$LFXMCP_CLIENT_ID" \ + --data "client_secret=" \ + --data "audience=$LFX_MCP_API_URL") MCP_TOKEN=$(echo "$MCP_TOKEN_RESPONSE" | jq -r '.access_token') if [ "$MCP_TOKEN" == "null" ] || [ -z "$MCP_TOKEN" ]; then - echo -e "${YELLOW}Warning: Could not get MCP token via client credentials. This is expected for token exchange clients.${NC}" - echo "You'll need to get a valid user token through the full OAuth flow." - echo "" - echo -e "${BLUE}Next Steps:${NC}" - echo "1. Use a browser or OAuth client to authenticate a user and get an MCP API token" - echo "2. Export the token: export MCP_USER_TOKEN=''" - echo "3. Run manual tests below:" - echo "" - echo " # Start server with token exchange:" - echo " LFXMCP_MODE=http \\" - echo " LFXMCP_HTTP_PORT=8081 \\" - echo " LFXMCP_MCP_API_AUTH_SERVERS=\"https://$AUTH0_DOMAIN\" \\" - echo " LFXMCP_MCP_API_PUBLIC_URL=\"$LFX_MCP_API_URL\" \\" - echo " LFXMCP_TOKEN_ENDPOINT=\"$TOKEN_ENDPOINT\" \\" - echo " LFXMCP_CLIENT_ID=\"\$LFXMCP_CLIENT_ID\" \\" - echo " LFXMCP_CLIENT_ASSERTION_SIGNING_KEY=\"\$(cat \$LFXMCP_CLIENT_ASSERTION_SIGNING_KEY_FILE)\" \\" - echo " LFXMCP_LFX_API_URL=\"$LFX_V2_API_URL\" \\" - echo " LFXMCP_TOOLS=user_info \\" - echo " LFXMCP_DEBUG=true \\" - echo " ./bin/lfx-mcp-server" - echo "" - echo " # Test user_info tool with your token:" - echo " curl -X POST http://127.0.0.1:8081/mcp \\" - echo " -H \"Content-Type: application/json\" \\" - echo " -H \"Authorization: Bearer \$MCP_USER_TOKEN\" \\" - echo " -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0.0\"}}}'" - echo "" - echo " curl -X POST http://127.0.0.1:8081/mcp \\" - echo " -H \"Content-Type: application/json\" \\" - echo " -H \"Authorization: Bearer \$MCP_USER_TOKEN\" \\" - echo " -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"user_info\",\"arguments\":{}}}'" - echo "" + echo -e "${YELLOW}Warning: Could not get MCP token via client credentials. This is expected for token exchange clients.${NC}" + echo "You'll need to get a valid user token through the full OAuth flow." + echo "" + echo -e "${BLUE}Next Steps:${NC}" + echo "1. Use a browser or OAuth client to authenticate a user and get an MCP API token" + echo "2. Export the token: export MCP_USER_TOKEN=''" + echo "3. Run manual tests below:" + echo "" + echo " # Start server with token exchange:" + echo " LFXMCP_MODE=http \\" + echo " LFXMCP_HTTP_PORT=8081 \\" + echo " LFXMCP_MCP_API_AUTH_SERVERS=\"https://$AUTH0_DOMAIN\" \\" + echo " LFXMCP_MCP_API_PUBLIC_URL=\"$LFX_MCP_API_URL\" \\" + echo " LFXMCP_TOKEN_ENDPOINT=\"$TOKEN_ENDPOINT\" \\" + echo " LFXMCP_CLIENT_ID=\"\$LFXMCP_CLIENT_ID\" \\" + echo " LFXMCP_CLIENT_ASSERTION_SIGNING_KEY=\"\$(cat \$LFXMCP_CLIENT_ASSERTION_SIGNING_KEY_FILE)\" \\" + echo " LFXMCP_LFX_API_URL=\"$LFX_V2_API_URL\" \\" + echo " LFXMCP_TOOLS=user_info \\" + echo " LFXMCP_DEBUG=true \\" + echo " ./bin/lfx-mcp-server" + echo "" + echo " # Test user_info tool with your token:" + echo " curl -X POST http://127.0.0.1:8081/mcp \\" + echo " -H \"Content-Type: application/json\" \\" + echo " -H \"Authorization: Bearer \$MCP_USER_TOKEN\" \\" + echo " -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0.0\"}}}'" + echo "" + echo " curl -X POST http://127.0.0.1:8081/mcp \\" + echo " -H \"Content-Type: application/json\" \\" + echo " -H \"Authorization: Bearer \$MCP_USER_TOKEN\" \\" + echo " -d '{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"user_info\",\"arguments\":{}}}'" + echo "" else - echo -e "${GREEN}Successfully obtained MCP token${NC}" - echo "Token preview: ${MCP_TOKEN:0:50}..." - echo "" - - # Start server with token exchange configuration. - echo "Starting server with token exchange configuration..." - LFXMCP_MODE=http \ - LFXMCP_HTTP_PORT=8081 \ - LFXMCP_MCP_API_AUTH_SERVERS="https://$AUTH0_DOMAIN" \ - LFXMCP_MCP_API_PUBLIC_URL="$LFX_MCP_API_URL" \ - LFXMCP_TOKEN_ENDPOINT="$TOKEN_ENDPOINT" \ - LFXMCP_CLIENT_ID="$LFXMCP_CLIENT_ID" \ - LFXMCP_CLIENT_ASSERTION_SIGNING_KEY="$PRIVATE_KEY" \ - LFXMCP_LFX_API_URL="$LFX_V2_API_URL" \ - LFXMCP_TOOLS=user_info \ - ./bin/lfx-mcp-server & - SERVER_PID=$! - - # Wait for server to start. - sleep 2 - - # Test user_info tool. - echo "Testing user_info tool..." - USER_INFO_RESPONSE=$(curl -s -X POST http://127.0.0.1:8081/mcp \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $MCP_TOKEN" \ - -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}') - - echo -e "${GREEN}Initialize Response:${NC}" - echo "$USER_INFO_RESPONSE" | jq '.' - - USER_INFO_RESPONSE=$(curl -s -X POST http://127.0.0.1:8081/mcp \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $MCP_TOKEN" \ - -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"user_info","arguments":{}}}') - - echo -e "${GREEN}user_info Tool Response:${NC}" - echo "$USER_INFO_RESPONSE" | jq '.' - - # Stop server. - kill $SERVER_PID - wait $SERVER_PID 2>/dev/null || true + echo -e "${GREEN}Successfully obtained MCP token${NC}" + echo "Token preview: ${MCP_TOKEN:0:50}..." + echo "" + + # Start server with token exchange configuration. + echo "Starting server with token exchange configuration..." + LFXMCP_MODE=http \ + LFXMCP_HTTP_PORT=8081 \ + LFXMCP_MCP_API_AUTH_SERVERS="https://$AUTH0_DOMAIN" \ + LFXMCP_MCP_API_PUBLIC_URL="$LFX_MCP_API_URL" \ + LFXMCP_TOKEN_ENDPOINT="$TOKEN_ENDPOINT" \ + LFXMCP_CLIENT_ID="$LFXMCP_CLIENT_ID" \ + LFXMCP_CLIENT_ASSERTION_SIGNING_KEY="$PRIVATE_KEY" \ + LFXMCP_LFX_API_URL="$LFX_V2_API_URL" \ + LFXMCP_TOOLS=user_info \ + ./bin/lfx-mcp-server & + SERVER_PID=$! + + # Wait for server to start. + sleep 2 + + # Test user_info tool. + echo "Testing user_info tool..." + USER_INFO_RESPONSE=$(curl -s -X POST http://127.0.0.1:8081/mcp \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $MCP_TOKEN" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}') + + echo -e "${GREEN}Initialize Response:${NC}" + echo "$USER_INFO_RESPONSE" | jq '.' + + USER_INFO_RESPONSE=$(curl -s -X POST http://127.0.0.1:8081/mcp \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $MCP_TOKEN" \ + -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"user_info","arguments":{}}}') + + echo -e "${GREEN}user_info Tool Response:${NC}" + echo "$USER_INFO_RESPONSE" | jq '.' + + # Stop server. + kill $SERVER_PID + wait $SERVER_PID 2>/dev/null || true fi echo "" diff --git a/scripts/test_prm.sh b/scripts/test_prm.sh index 8308b09..722bebf 100755 --- a/scripts/test_prm.sh +++ b/scripts/test_prm.sh @@ -9,10 +9,10 @@ set -e # Start server in background with MCP API configured ./bin/lfx-mcp-server \ - -mode=http \ - -http.port=8081 \ - -mcp_api.auth_servers=https://dev-lfx.us.auth0.com \ - -mcp_api.public_url=https://api-dev.lfx.linuxfoundation.org/mcp & + -mode=http \ + -http.port=8081 \ + -mcp_api.auth_servers=https://dev-lfx.us.auth0.com \ + -mcp_api.public_url=https://api-dev.lfx.linuxfoundation.org/mcp & SERVER_PID=$! echo "Started server with PID $SERVER_PID" diff --git a/scripts/test_server.sh b/scripts/test_server.sh index bb2c941..54bc0a3 100755 --- a/scripts/test_server.sh +++ b/scripts/test_server.sh @@ -8,8 +8,8 @@ set -e # Parse command line arguments DEBUG_FLAG="" if [[ "$1" == "--debug" || "$1" == "-d" ]]; then - DEBUG_FLAG="-debug" - echo "Running tests with debug logging enabled..." + DEBUG_FLAG="-debug" + echo "Running tests with debug logging enabled..." fi echo "Testing LFX MCP Server..." @@ -89,7 +89,7 @@ echo "" echo "All tests completed successfully! πŸŽ‰" echo "" if [[ -n "$DEBUG_FLAG" ]]; then - echo "Tests ran with debug logging enabled (logs on stderr)" + echo "Tests ran with debug logging enabled (logs on stderr)" fi echo "" echo "The LFX MCP Server is working correctly with:"