From 1d1a0b62080525440f375fb4a28567984a0cdd2a Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Mon, 16 Feb 2026 14:07:27 +0100 Subject: [PATCH] Add list attendees to mcp meetings Signed-off-by: Sacha Al Himdani --- pkg/server/api/mcp/v1/schema.resolvers.go | 20 ++++++++++++++ pkg/server/api/mcp/v1/server/server.go | 15 +++++++++++ pkg/server/api/mcp/v1/specification.yaml | 29 ++++++++++++++++++++ pkg/server/api/mcp/v1/types/profile.go | 33 +++++++++++++++++++++++ pkg/server/api/mcp/v1/types/types.go | 14 ++++++++++ 5 files changed, 111 insertions(+) create mode 100644 pkg/server/api/mcp/v1/types/profile.go diff --git a/pkg/server/api/mcp/v1/schema.resolvers.go b/pkg/server/api/mcp/v1/schema.resolvers.go index 7d1e0d525..9555a0ed6 100644 --- a/pkg/server/api/mcp/v1/schema.resolvers.go +++ b/pkg/server/api/mcp/v1/schema.resolvers.go @@ -1956,3 +1956,23 @@ func (r *Resolver) DeleteRiskTool(ctx context.Context, req *mcp.CallToolRequest, DeletedRiskID: input.ID, }, nil } + +func (r *Resolver) ListMeetingAttendeesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListMeetingAttendeesInput) (*mcp.CallToolResult, types.ListMeetingAttendeesOutput, error) { + r.MustAuthorize(ctx, input.MeetingID, probo.ActionMeetingGet) + + svc := r.ProboService(ctx, input.MeetingID) + + attendees, err := svc.Meetings.GetAttendees(ctx, input.MeetingID) + if err != nil { + return nil, types.ListMeetingAttendeesOutput{}, fmt.Errorf("failed to list meeting attendees: %w", err) + } + + profiles := make([]*types.Profile, 0, len(attendees)) + for _, a := range attendees { + profiles = append(profiles, types.NewProfile(a)) + } + + return nil, types.ListMeetingAttendeesOutput{ + Attendees: profiles, + }, nil +} diff --git a/pkg/server/api/mcp/v1/server/server.go b/pkg/server/api/mcp/v1/server/server.go index 6626a4bbc..babbfedc1 100644 --- a/pkg/server/api/mcp/v1/server/server.go +++ b/pkg/server/api/mcp/v1/server/server.go @@ -95,6 +95,7 @@ type ResolverInterface interface { AddMeetingTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddMeetingInput) (*mcp.CallToolResult, types.AddMeetingOutput, error) UpdateMeetingTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateMeetingInput) (*mcp.CallToolResult, types.UpdateMeetingOutput, error) DeleteMeetingTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteMeetingInput) (*mcp.CallToolResult, types.DeleteMeetingOutput, error) + ListMeetingAttendeesTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListMeetingAttendeesInput) (*mcp.CallToolResult, types.ListMeetingAttendeesOutput, error) } // New creates a new MCP server instance with all handlers registered. @@ -1120,6 +1121,20 @@ func registerToolHandlers(server *mcp.Server, resolver ResolverInterface) { }, resolver.DeleteMeetingTool, ) + mcp.AddTool( + server, + &mcp.Tool{ + Name: "listMeetingAttendees", + Description: "List all attendees for a meeting", + InputSchema: types.ListMeetingAttendeesToolInputSchema, + OutputSchema: types.ListMeetingAttendeesToolOutputSchema, + Annotations: &mcp.ToolAnnotations{ + ReadOnlyHint: true, + IdempotentHint: true, + }, + }, + resolver.ListMeetingAttendeesTool, + ) } func boolPtr(b bool) *bool { diff --git a/pkg/server/api/mcp/v1/specification.yaml b/pkg/server/api/mcp/v1/specification.yaml index d2e633f0a..3e67ce127 100644 --- a/pkg/server/api/mcp/v1/specification.yaml +++ b/pkg/server/api/mcp/v1/specification.yaml @@ -4354,6 +4354,26 @@ components: $ref: "#/components/schemas/GID" description: Deleted meeting ID + ListMeetingAttendeesInput: + type: object + required: + - meeting_id + properties: + meeting_id: + $ref: "#/components/schemas/GID" + description: Meeting ID + + ListMeetingAttendeesOutput: + type: object + required: + - attendees + properties: + attendees: + type: array + items: + $ref: "#/components/schemas/Profile" + description: List of attendee profiles + tools: - name: listOrganizations description: List all organizations the user has access to @@ -5075,3 +5095,12 @@ tools: $ref: "#/components/schemas/DeleteMeetingInput" outputSchema: $ref: "#/components/schemas/DeleteMeetingOutput" + - name: listMeetingAttendees + description: List all attendees for a meeting + hints: + readonly: true + idempotent: true + inputSchema: + $ref: "#/components/schemas/ListMeetingAttendeesInput" + outputSchema: + $ref: "#/components/schemas/ListMeetingAttendeesOutput" diff --git a/pkg/server/api/mcp/v1/types/profile.go b/pkg/server/api/mcp/v1/types/profile.go new file mode 100644 index 000000000..7e5a961dc --- /dev/null +++ b/pkg/server/api/mcp/v1/types/profile.go @@ -0,0 +1,33 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import "go.probo.inc/probo/pkg/coredata" + +func NewProfile(p *coredata.MembershipProfile) *Profile { + return &Profile{ + ID: p.ID, + OrganizationID: p.OrganizationID, + FullName: p.FullName, + EmailAddress: p.EmailAddress, + AdditionalEmailAddresses: p.AdditionalEmailAddresses, + Kind: p.Kind, + Position: p.Position, + ContractStartDate: p.ContractStartDate, + ContractEndDate: p.ContractEndDate, + CreatedAt: p.CreatedAt, + UpdatedAt: p.UpdatedAt, + } +} diff --git a/pkg/server/api/mcp/v1/types/types.go b/pkg/server/api/mcp/v1/types/types.go index a91dd4eb2..8cfaf5b24 100644 --- a/pkg/server/api/mcp/v1/types/types.go +++ b/pkg/server/api/mcp/v1/types/types.go @@ -119,6 +119,8 @@ var ( ListFrameworksToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"frameworks":{"type":"array","items":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Framework description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Framework name"},"organization_id":{"type":"string","format":"string"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","name","created_at","updated_at"]}},"next_cursor":{"type":"string","format":"string"}},"required":["frameworks"]}`) ListMeasuresToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"query":{"type":"string","description":"Search query"},"state":{"type":"string","enum":["NOT_STARTED","IN_PROGRESS","NOT_APPLICABLE","IMPLEMENTED"]}}},"order_by":{"type":"object","properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","NAME"]}},"required":["field","direction"]},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}},"required":["organization_id"]}`) ListMeasuresToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"measures":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string","description":"Measure category"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"description":{"description":"Measure description"},"id":{"type":"string","format":"string"},"name":{"type":"string","description":"Measure name"},"state":{"type":"string","enum":["NOT_STARTED","IN_PROGRESS","NOT_APPLICABLE","IMPLEMENTED"]},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","category","name","state","created_at","updated_at"]}},"next_cursor":{"type":"string","format":"string"}},"required":["measures"]}`) + ListMeetingAttendeesToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"meeting_id":{"type":"string","format":"string"}},"required":["meeting_id"]}`) + ListMeetingAttendeesToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"attendees":{"type":"array","items":{"type":"object","properties":{"additional_email_addresses":{"type":"array","items":{"type":"string","format":"string"},"description":"Additional email addresses"},"contract_end_date":{"description":"Contract end date","format":"date-time"},"contract_start_date":{"description":"Contract start date","format":"date-time"},"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"email_address":{"type":"string","format":"string"},"full_name":{"type":"string","description":"Full name"},"id":{"type":"string","format":"string"},"kind":{"type":"string","enum":["EMPLOYEE","CONTRACTOR","SERVICE_ACCOUNT"]},"organization_id":{"type":"string","format":"string"},"position":{"description":"Position"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","organization_id","full_name","email_address","additional_email_addresses","kind","created_at","updated_at"]},"description":"List of attendee profiles"}},"required":["attendees"]}`) ListMeetingsToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"cursor":{"type":"string","format":"string"},"order_by":{"type":"object","properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","DATE","NAME"]}},"required":["field","direction"]},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}},"required":["organization_id"]}`) ListMeetingsToolOutputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"meetings":{"type":"array","items":{"type":"object","properties":{"created_at":{"type":"string","description":"Creation timestamp","format":"date-time"},"date":{"type":"string","description":"Meeting date","format":"date-time"},"id":{"type":"string","format":"string"},"minutes":{"description":"Meeting minutes","anyOf":[{"type":"string","description":"Meeting minutes"},{"type":"null","description":"No minutes"}]},"name":{"type":"string","description":"Meeting name"},"updated_at":{"type":"string","description":"Update timestamp","format":"date-time"}},"required":["id","name","date","created_at","updated_at"]},"description":"List of meetings"},"next_cursor":{"description":"Next page cursor","anyOf":[{"type":"string","format":"string"},{"type":"null"}]}},"required":["meetings"]}`) ListNonconformitiesToolInputSchema = mcp.MustUnmarshalSchema(`{"type":"object","properties":{"cursor":{"type":"string","format":"string"},"filter":{"type":"object","properties":{"snapshot_id":{"type":"string","format":"string"}}},"order_by":{"type":"object","properties":{"direction":{"type":"string","enum":["ASC","DESC"]},"field":{"type":"string","enum":["CREATED_AT","REFERENCE_ID","DATE_IDENTIFIED","DUE_DATE","STATUS"]}},"required":["field","direction"]},"organization_id":{"type":"string","format":"string"},"size":{"type":"integer","description":"Page size"}},"required":["organization_id"]}`) @@ -1642,6 +1644,18 @@ type ListMeasuresOutput struct { NextCursor *page.CursorKey `json:"next_cursor,omitempty"` } +// ListMeetingAttendeesInput represents the schema +type ListMeetingAttendeesInput struct { + // Meeting ID + MeetingID gid.GID `json:"meeting_id"` +} + +// ListMeetingAttendeesOutput represents the schema +type ListMeetingAttendeesOutput struct { + // List of attendee profiles + Attendees []*Profile `json:"attendees"` +} + // ListMeetingsInput represents the schema type ListMeetingsInput struct { // Page cursor