Fix people in mcp

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-01-02 10:20:13 +01:00
parent 6ab5ace353
commit aba51629c0
12 changed files with 69 additions and 36 deletions

View File

@@ -77,6 +77,7 @@ export function CreatePeopleDialog({ children, connectionId }: Props) {
input: {
...data,
organizationId,
additionalEmailAddresses: data.additionalEmailAddresses ?? [],
},
connections: [connectionId],
},

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<22054346a606e1ce4a26b02218f87af9>>
* @generated SignedSource<<9acf6a96c0398ea5ae6fec887e8d01b1>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -11,7 +11,7 @@
import { ConcreteRequest } from 'relay-runtime';
export type PeopleKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT";
export type CreatePeopleInput = {
additionalEmailAddresses?: ReadonlyArray<any> | null | undefined;
additionalEmailAddresses: ReadonlyArray<any>;
contractEndDate?: any | null | undefined;
contractStartDate?: any | null | undefined;
fullName: string;

View File

@@ -58,11 +58,12 @@ func TestPeople_Create(t *testing.T) {
err := owner.Execute(query, map[string]any{
"input": map[string]any{
"organizationId": owner.GetOrganizationID().String(),
"fullName": "John Doe",
"primaryEmailAddress": "john.doe@example.com",
"kind": "EMPLOYEE",
"position": "Software Engineer",
"organizationId": owner.GetOrganizationID().String(),
"fullName": "John Doe",
"primaryEmailAddress": "john.doe@example.com",
"additionalEmailAddresses": []string{},
"kind": "EMPLOYEE",
"position": "Software Engineer",
},
}, &result)
require.NoError(t, err)
@@ -253,9 +254,10 @@ func TestPeople_RequiredFields(t *testing.T) {
{
name: "missing organizationId",
input: map[string]any{
"fullName": "Test Person",
"primaryEmailAddress": "test@example.com",
"kind": "EMPLOYEE",
"fullName": "Test Person",
"primaryEmailAddress": "test@example.com",
"additionalEmailAddresses": []string{},
"kind": "EMPLOYEE",
},
skipOrganization: true,
wantErrorContains: "organizationId",
@@ -263,42 +265,47 @@ func TestPeople_RequiredFields(t *testing.T) {
{
name: "missing fullName",
input: map[string]any{
"primaryEmailAddress": "test@example.com",
"kind": "EMPLOYEE",
"primaryEmailAddress": "test@example.com",
"additionalEmailAddresses": []string{},
"kind": "EMPLOYEE",
},
wantErrorContains: "fullName",
},
{
name: "missing primaryEmailAddress",
input: map[string]any{
"fullName": "Test Person",
"kind": "EMPLOYEE",
"fullName": "Test Person",
"additionalEmailAddresses": []string{},
"kind": "EMPLOYEE",
},
wantErrorContains: "primaryEmailAddress",
},
{
name: "missing kind",
input: map[string]any{
"fullName": "Test Person",
"primaryEmailAddress": "test@example.com",
"fullName": "Test Person",
"primaryEmailAddress": "test@example.com",
"additionalEmailAddresses": []string{},
},
wantErrorContains: "kind",
},
{
name: "empty fullName",
input: map[string]any{
"fullName": "",
"primaryEmailAddress": "test@example.com",
"kind": "EMPLOYEE",
"fullName": "",
"primaryEmailAddress": "test@example.com",
"additionalEmailAddresses": []string{},
"kind": "EMPLOYEE",
},
wantErrorContains: "full_name",
},
{
name: "invalid kind enum",
input: map[string]any{
"fullName": "Test Person",
"primaryEmailAddress": "test@example.com",
"kind": "INVALID_KIND",
"fullName": "Test Person",
"primaryEmailAddress": "test@example.com",
"additionalEmailAddresses": []string{},
"kind": "INVALID_KIND",
},
wantErrorContains: "kind",
},

View File

@@ -908,7 +908,7 @@ func TestRBAC(t *testing.T) {
client: owner,
query: createPeopleMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "fullName": factory.SafeName("Person"), "primaryEmailAddress": factory.SafeEmail(), "kind": "EMPLOYEE"}}
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "fullName": factory.SafeName("Person"), "primaryEmailAddress": factory.SafeEmail(), "additionalEmailAddresses": []string{}, "kind": "EMPLOYEE"}}
},
shouldAllow: true,
},
@@ -918,7 +918,7 @@ func TestRBAC(t *testing.T) {
client: admin,
query: createPeopleMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "fullName": factory.SafeName("Person"), "primaryEmailAddress": factory.SafeEmail(), "kind": "EMPLOYEE"}}
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "fullName": factory.SafeName("Person"), "primaryEmailAddress": factory.SafeEmail(), "additionalEmailAddresses": []string{}, "kind": "EMPLOYEE"}}
},
shouldAllow: true,
},
@@ -928,7 +928,7 @@ func TestRBAC(t *testing.T) {
client: viewer,
query: createPeopleMutation,
variables: func() map[string]any {
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "fullName": factory.SafeName("Person"), "primaryEmailAddress": factory.SafeEmail(), "kind": "EMPLOYEE"}}
return map[string]any{"input": map[string]any{"organizationId": owner.GetOrganizationID().String(), "fullName": factory.SafeName("Person"), "primaryEmailAddress": factory.SafeEmail(), "additionalEmailAddresses": []string{}, "kind": "EMPLOYEE"}}
},
shouldAllow: false,
},
@@ -1259,4 +1259,3 @@ func TestRBAC(t *testing.T) {
)
}
}

View File

@@ -92,6 +92,18 @@ func (a Attrs) getBool(key string, defaultVal bool) bool {
return defaultVal
}
func (a Attrs) getSlice(key string, defaultVal []string) []string {
if a == nil {
return defaultVal
}
if v, ok := a[key]; ok {
if slice, ok := v.([]string); ok {
return slice
}
}
return defaultVal
}
func CreateVendor(c *testutil.Client, attrs ...Attrs) string {
c.T.Helper()
@@ -375,10 +387,11 @@ func CreatePeople(c *testutil.Client, attrs ...Attrs) string {
`
input := map[string]any{
"organizationId": c.GetOrganizationID().String(),
"fullName": a.getString("fullName", SafeName("Person")),
"primaryEmailAddress": a.getString("primaryEmailAddress", SafeEmail()),
"kind": a.getString("kind", "EMPLOYEE"),
"organizationId": c.GetOrganizationID().String(),
"fullName": a.getString("fullName", SafeName("Person")),
"primaryEmailAddress": a.getString("primaryEmailAddress", SafeEmail()),
"additionalEmailAddresses": a.getSlice("additionalEmailAddresses", []string{}),
"kind": a.getString("kind", "EMPLOYEE"),
}
var result struct {

View File

@@ -159,6 +159,8 @@ export async function execute(
};
if (additionalFields.additionalEmailAddresses) {
input.additionalEmailAddresses = additionalFields.additionalEmailAddresses.split(',').map((e) => e.trim()).filter(Boolean);
} else {
input.additionalEmailAddresses = [];
}
if (additionalFields.position) input.position = additionalFields.position;
if (additionalFields.contractStartDate) input.contractStartDate = new Date(additionalFields.contractStartDate).toISOString();

View File

@@ -0,0 +1,5 @@
UPDATE peoples
SET additional_email_addresses = ARRAY[]::TEXT[]
WHERE additional_email_addresses IS NULL;
ALTER TABLE peoples ALTER COLUMN additional_email_addresses SET NOT NULL;

View File

@@ -3523,7 +3523,7 @@ input CreatePeopleInput {
organizationId: ID!
fullName: String!
primaryEmailAddress: EmailAddr!
additionalEmailAddresses: [EmailAddr!]
additionalEmailAddresses: [EmailAddr!]!
kind: PeopleKind!
position: String
contractStartDate: Datetime

View File

@@ -13522,7 +13522,7 @@ input CreatePeopleInput {
organizationId: ID!
fullName: String!
primaryEmailAddress: EmailAddr!
additionalEmailAddresses: [EmailAddr!]
additionalEmailAddresses: [EmailAddr!]!
kind: PeopleKind!
position: String
contractStartDate: Datetime
@@ -60752,7 +60752,7 @@ func (ec *executionContext) unmarshalInputCreatePeopleInput(ctx context.Context,
it.PrimaryEmailAddress = data
case "additionalEmailAddresses":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("additionalEmailAddresses"))
data, err := ec.unmarshalOEmailAddr2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ(ctx, v)
data, err := ec.unmarshalNEmailAddr2ᚕgoᚗproboᚗincᚋproboᚋpkgᚋmailᚐAddrᚄ(ctx, v)
if err != nil {
return it, err
}

View File

@@ -425,7 +425,7 @@ type CreatePeopleInput struct {
OrganizationID gid.GID `json:"organizationId"`
FullName string `json:"fullName"`
PrimaryEmailAddress mail.Addr `json:"primaryEmailAddress"`
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses,omitempty"`
AdditionalEmailAddresses []mail.Addr `json:"additionalEmailAddresses"`
Kind coredata.PeopleKind `json:"kind"`
Position *string `json:"position,omitempty"`
ContractStartDate *time.Time `json:"contractStartDate,omitempty"`

View File

@@ -2073,7 +2073,7 @@ func (r *mutationResolver) CreatePeople(ctx context.Context, input types.CreateP
OrganizationID: input.OrganizationID,
FullName: input.FullName,
PrimaryEmailAddress: input.PrimaryEmailAddress,
AdditionalEmailAddresses: []mail.Addr{},
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
Kind: input.Kind,
Position: input.Position,
ContractStartDate: input.ContractStartDate,

View File

@@ -11,6 +11,7 @@ import (
"github.com/modelcontextprotocol/go-sdk/mcp"
"go.probo.inc/probo/pkg/authz"
"go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/page"
"go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/server/api/mcp/v1/types"
@@ -171,13 +172,18 @@ func (r *Resolver) AddPeopleTool(ctx context.Context, req *mcp.CallToolRequest,
svc := r.ProboService(ctx, input.OrganizationID)
additionalEmails := []mail.Addr{}
if input.AdditionalEmailAddresses != nil {
additionalEmails = input.AdditionalEmailAddresses
}
people, err := svc.Peoples.Create(
ctx,
probo.CreatePeopleRequest{
OrganizationID: input.OrganizationID,
FullName: input.FullName,
PrimaryEmailAddress: input.PrimaryEmailAddress,
AdditionalEmailAddresses: input.AdditionalEmailAddresses,
AdditionalEmailAddresses: additionalEmails,
Kind: input.Kind,
Position: input.Position,
ContractStartDate: input.ContractStartDate,