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: { input: {
...data, ...data,
organizationId, organizationId,
additionalEmailAddresses: data.additionalEmailAddresses ?? [],
}, },
connections: [connectionId], connections: [connectionId],
}, },

View File

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

View File

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

View File

@@ -908,7 +908,7 @@ func TestRBAC(t *testing.T) {
client: owner, client: owner,
query: createPeopleMutation, query: createPeopleMutation,
variables: func() map[string]any { 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, shouldAllow: true,
}, },
@@ -918,7 +918,7 @@ func TestRBAC(t *testing.T) {
client: admin, client: admin,
query: createPeopleMutation, query: createPeopleMutation,
variables: func() map[string]any { 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, shouldAllow: true,
}, },
@@ -928,7 +928,7 @@ func TestRBAC(t *testing.T) {
client: viewer, client: viewer,
query: createPeopleMutation, query: createPeopleMutation,
variables: func() map[string]any { 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, 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 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 { func CreateVendor(c *testutil.Client, attrs ...Attrs) string {
c.T.Helper() c.T.Helper()
@@ -375,10 +387,11 @@ func CreatePeople(c *testutil.Client, attrs ...Attrs) string {
` `
input := map[string]any{ input := map[string]any{
"organizationId": c.GetOrganizationID().String(), "organizationId": c.GetOrganizationID().String(),
"fullName": a.getString("fullName", SafeName("Person")), "fullName": a.getString("fullName", SafeName("Person")),
"primaryEmailAddress": a.getString("primaryEmailAddress", SafeEmail()), "primaryEmailAddress": a.getString("primaryEmailAddress", SafeEmail()),
"kind": a.getString("kind", "EMPLOYEE"), "additionalEmailAddresses": a.getSlice("additionalEmailAddresses", []string{}),
"kind": a.getString("kind", "EMPLOYEE"),
} }
var result struct { var result struct {

View File

@@ -159,6 +159,8 @@ export async function execute(
}; };
if (additionalFields.additionalEmailAddresses) { if (additionalFields.additionalEmailAddresses) {
input.additionalEmailAddresses = additionalFields.additionalEmailAddresses.split(',').map((e) => e.trim()).filter(Boolean); input.additionalEmailAddresses = additionalFields.additionalEmailAddresses.split(',').map((e) => e.trim()).filter(Boolean);
} else {
input.additionalEmailAddresses = [];
} }
if (additionalFields.position) input.position = additionalFields.position; if (additionalFields.position) input.position = additionalFields.position;
if (additionalFields.contractStartDate) input.contractStartDate = new Date(additionalFields.contractStartDate).toISOString(); 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! organizationId: ID!
fullName: String! fullName: String!
primaryEmailAddress: EmailAddr! primaryEmailAddress: EmailAddr!
additionalEmailAddresses: [EmailAddr!] additionalEmailAddresses: [EmailAddr!]!
kind: PeopleKind! kind: PeopleKind!
position: String position: String
contractStartDate: Datetime contractStartDate: Datetime

View File

@@ -13522,7 +13522,7 @@ input CreatePeopleInput {
organizationId: ID! organizationId: ID!
fullName: String! fullName: String!
primaryEmailAddress: EmailAddr! primaryEmailAddress: EmailAddr!
additionalEmailAddresses: [EmailAddr!] additionalEmailAddresses: [EmailAddr!]!
kind: PeopleKind! kind: PeopleKind!
position: String position: String
contractStartDate: Datetime contractStartDate: Datetime
@@ -60752,7 +60752,7 @@ func (ec *executionContext) unmarshalInputCreatePeopleInput(ctx context.Context,
it.PrimaryEmailAddress = data it.PrimaryEmailAddress = data
case "additionalEmailAddresses": case "additionalEmailAddresses":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("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 { if err != nil {
return it, err return it, err
} }

View File

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

View File

@@ -11,6 +11,7 @@ import (
"github.com/modelcontextprotocol/go-sdk/mcp" "github.com/modelcontextprotocol/go-sdk/mcp"
"go.probo.inc/probo/pkg/authz" "go.probo.inc/probo/pkg/authz"
"go.probo.inc/probo/pkg/coredata" "go.probo.inc/probo/pkg/coredata"
"go.probo.inc/probo/pkg/mail"
"go.probo.inc/probo/pkg/page" "go.probo.inc/probo/pkg/page"
"go.probo.inc/probo/pkg/probo" "go.probo.inc/probo/pkg/probo"
"go.probo.inc/probo/pkg/server/api/mcp/v1/types" "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) svc := r.ProboService(ctx, input.OrganizationID)
additionalEmails := []mail.Addr{}
if input.AdditionalEmailAddresses != nil {
additionalEmails = input.AdditionalEmailAddresses
}
people, err := svc.Peoples.Create( people, err := svc.Peoples.Create(
ctx, ctx,
probo.CreatePeopleRequest{ probo.CreatePeopleRequest{
OrganizationID: input.OrganizationID, OrganizationID: input.OrganizationID,
FullName: input.FullName, FullName: input.FullName,
PrimaryEmailAddress: input.PrimaryEmailAddress, PrimaryEmailAddress: input.PrimaryEmailAddress,
AdditionalEmailAddresses: input.AdditionalEmailAddresses, AdditionalEmailAddresses: additionalEmails,
Kind: input.Kind, Kind: input.Kind,
Position: input.Position, Position: input.Position,
ContractStartDate: input.ContractStartDate, ContractStartDate: input.ContractStartDate,