Skip to content

feat: REST API, MCP server, and team-scoped API tokens#136

Merged
ManukMinasyan merged 160 commits intomainfrom
feat/rest-api-mcp-server
Mar 29, 2026
Merged

feat: REST API, MCP server, and team-scoped API tokens#136
ManukMinasyan merged 160 commits intomainfrom
feat/rest-api-mcp-server

Conversation

@ManukMinasyan
Copy link
Copy Markdown
Contributor

@ManukMinasyan ManukMinasyan commented Feb 20, 2026

Summary

Full REST API (JSON:API format) and MCP server for all CRM entities, plus team-scoped API tokens with expiration.

REST API (v1)

  • CRUD endpoints for companies, people, opportunities, tasks, and notes
  • JSON:API response format via Laravel's JsonApiResource
  • Spatie QueryBuilder for filtering, sorting, and pagination
  • Custom fields readable via dedicated endpoint
  • ForceJsonResponse middleware scoped to v1 routes
  • SetApiTeamContext middleware with Octane-safe scope cleanup

MCP Server

  • Full MCP tool suite for all CRM entities (create, list, get, update, delete)
  • Notes tools with polymorphic notable support
  • Team context resolved from API token

Team-Scoped API Tokens

  • Tokens permanently scoped to a specific team at creation time
  • GitHub-style expiration options (1 day to 1 year, or no expiration)
  • Team resolved from token first, then header, then user's current team
  • Migration adds team_id to personal_access_tokens
  • Custom PersonalAccessToken model with team() relationship

Security & Hardening

  • entity_type validation in CustomFieldsController
  • Observer ??= to prevent overwriting pre-set values (API vs UI)
  • UpdateTask decoupled from Filament panel context
  • CreationSource::API badge color changed from danger to info

Test Plan

  • API CRUD tests for all 5 entities (companies, people, opportunities, tasks, notes)
  • Filtering, sorting, and pagination tests
  • Custom fields endpoint tests
  • Include/relationship tests
  • API token creation with team selector and expiration
  • API token management table with team and expiration columns
  • Team membership authorization tests
  • 106 passed, 1 pre-existing failure (TasksApiTest include creator — unrelated)

Closes #85
Closes #89
Closes #133


Note

High Risk
Introduces new externally facing REST + MCP surfaces and changes Sanctum token scoping/ability enforcement via middleware, which is security- and multi-tenancy-critical and could cause authorization or data-leak regressions if misconfigured.

Overview
Adds a new api/v1 JSON:API REST surface for core CRM entities (companies/people/opportunities/tasks/notes) plus a custom-fields endpoint, with request validation, resource serialization, and list pagination/filtering wired through a shared Action layer.

Introduces an MCP server (RelaticleServer) exposing schema resources, a CRM overview prompt, and CRUD/list tools; refactors MCP tools around shared base classes and enforces token abilities for both API (new EnsureTokenHasAbility middleware) and MCP tool execution.

Hardens production readiness: makes laravel/mcp a production dependency, scopes requests to teams via SetApiTeamContext (Octane-safe cleanup and token/header team resolution), adds pagination to previously unbounded endpoints, adds short TTL caching for MCP schema/prompt reads, tightens mass-assignment via Arr::only() in create/update actions, and reduces /api/user exposure by returning UserResource.

Expands test coverage substantially around token abilities, token team scoping and expiry, pagination/filtering/sorting parity across entities, MCP tool features, and token team_id immutability.

Written by Cursor Bugbot for commit 8f9e27b. This will update automatically on new commits. Configure here.

Introduce a shared Actions layer for business logic (CRUD for companies,
people, opportunities, tasks, notes) consumed by both a versioned REST
API (Sanctum auth, rate limiting, team scoping) and a Laravel MCP server
with 20 tools, a schema resource, and overview prompt.

Includes SetApiTeamContext middleware that bridges Sanctum auth to the
web guard so observers and TeamScope work unchanged, API Resources with
custom field serialization, and 58 feature tests covering both surfaces.

Closes #85, closes #89
Copilot AI review requested due to automatic review settings February 20, 2026 00:32
@ManukMinasyan ManukMinasyan self-assigned this Feb 20, 2026
- Add App\Mcp and App\Http\Resources to arch test ignore lists
  (MCP classes extend framework base classes, Resources extend JsonResource)
- Apply Rector rules: abort_unless(), query builder methods,
  arrow functions, class constants, removed unnecessary parentheses
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a Sanctum-authenticated REST API (v1) and an MCP server to expose core CRM entities (companies, people, opportunities, tasks, notes) to external integrations and AI agents, while attempting to preserve existing tenant scoping/policy behavior via a dedicated API team-context middleware.

Changes:

  • Introduces /api/v1/* CRUD endpoints with controllers, form requests, and JSON resources.
  • Adds an MCP server (/mcp/relaticle) with tools for CRUD/listing, plus a CRM schema resource and overview prompt.
  • Adds an Actions layer for shared business logic + rate limiting configuration + feature tests.

Reviewed changes

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

Show a summary per file
File Description
tests/Feature/Mcp/RelaticleServerTest.php MCP server/tool smoke tests for listing and CRUD behavior.
tests/Feature/Api/V1/TasksApiTest.php REST API tests for tasks CRUD, auth, and team scoping.
tests/Feature/Api/V1/PeopleApiTest.php REST API tests for people CRUD, auth, and team scoping.
tests/Feature/Api/V1/OpportunitiesApiTest.php REST API tests for opportunities CRUD, auth, and team scoping.
tests/Feature/Api/V1/NotesApiTest.php REST API tests for notes CRUD, auth, and team scoping.
tests/Feature/Api/V1/CompaniesApiTest.php REST API tests for companies CRUD, auth, and team scoping.
tests/Feature/Api/V1/ApiTeamScopingTest.php Tests switching teams via X-Team-Id and rejection cases.
routes/api.php Registers versioned API resources with auth/scoping/rate limit middleware.
routes/ai.php Registers MCP web endpoint protected by Sanctum + team context middleware.
phpstan.neon Suppresses PHPStan attribute warnings under app/Mcp.
bootstrap/app.php Prepends API throttling middleware to the API middleware group.
app/Providers/AppServiceProvider.php Defines RateLimiter::for('api') limiter configuration.
app/Observers/TaskObserver.php Uses default auth context when setting creator/team on create.
app/Observers/PeopleObserver.php Uses default auth context when setting creator/team on create.
app/Observers/OpportunityObserver.php Uses default auth context when setting creator/team on create.
app/Models/Scopes/TeamScope.php Uses default auth context for whereBelongsTo(currentTeam).
app/Mcp/Tools/Task/UpdateTaskTool.php MCP tool to update tasks via Actions + resource output.
app/Mcp/Tools/Task/ListTasksTool.php MCP tool to list tasks via Actions + resource output.
app/Mcp/Tools/Task/DeleteTaskTool.php MCP tool to delete tasks via Actions.
app/Mcp/Tools/Task/CreateTaskTool.php MCP tool to create tasks via Actions.
app/Mcp/Tools/People/UpdatePeopleTool.php MCP tool to update people via Actions + resource output.
app/Mcp/Tools/People/ListPeopleTool.php MCP tool to list people via Actions + resource output.
app/Mcp/Tools/People/DeletePeopleTool.php MCP tool to delete people via Actions.
app/Mcp/Tools/People/CreatePeopleTool.php MCP tool to create people via Actions.
app/Mcp/Tools/Opportunity/UpdateOpportunityTool.php MCP tool to update opportunities via Actions + resource output.
app/Mcp/Tools/Opportunity/ListOpportunitiesTool.php MCP tool to list opportunities via Actions + resource output.
app/Mcp/Tools/Opportunity/DeleteOpportunityTool.php MCP tool to delete opportunities via Actions.
app/Mcp/Tools/Opportunity/CreateOpportunityTool.php MCP tool to create opportunities via Actions.
app/Mcp/Tools/Note/UpdateNoteTool.php MCP tool to update notes via Actions + resource output.
app/Mcp/Tools/Note/ListNotesTool.php MCP tool to list notes via Actions + resource output.
app/Mcp/Tools/Note/DeleteNoteTool.php MCP tool to delete notes via Actions.
app/Mcp/Tools/Note/CreateNoteTool.php MCP tool to create notes via Actions.
app/Mcp/Tools/Company/UpdateCompanyTool.php MCP tool to update companies via Actions + resource output.
app/Mcp/Tools/Company/ListCompaniesTool.php MCP tool to list companies via Actions + resource output.
app/Mcp/Tools/Company/DeleteCompanyTool.php MCP tool to delete companies via Actions.
app/Mcp/Tools/Company/CreateCompanyTool.php MCP tool to create companies via Actions.
app/Mcp/Servers/RelaticleServer.php MCP server wiring for tools/resources/prompts.
app/Mcp/Resources/CrmSchemaResource.php Provides a JSON schema-like description of CRM entities/fields.
app/Mcp/Prompts/CrmOverviewPrompt.php Provides a human-readable CRM overview prompt.
app/Http/Resources/V1/UserResource.php Serializes user info for nested API resource responses.
app/Http/Resources/V1/TaskResource.php Serializes tasks including custom fields and relationships.
app/Http/Resources/V1/PeopleResource.php Serializes people including custom fields and relationships.
app/Http/Resources/V1/OpportunityResource.php Serializes opportunities including custom fields and relationships.
app/Http/Resources/V1/NoteResource.php Serializes notes including custom fields and relationships.
app/Http/Resources/V1/Concerns/FormatsCustomFields.php Shared formatting of custom field values into key/value JSON.
app/Http/Resources/V1/CompanyResource.php Serializes companies including custom fields and relationships.
app/Http/Requests/Api/V1/UpdateTaskRequest.php Validation rules for updating tasks via API.
app/Http/Requests/Api/V1/UpdatePeopleRequest.php Validation rules for updating people via API.
app/Http/Requests/Api/V1/UpdateOpportunityRequest.php Validation rules for updating opportunities via API.
app/Http/Requests/Api/V1/UpdateNoteRequest.php Validation rules for updating notes via API.
app/Http/Requests/Api/V1/UpdateCompanyRequest.php Validation rules for updating companies via API.
app/Http/Requests/Api/V1/StoreTaskRequest.php Validation rules for creating tasks via API.
app/Http/Requests/Api/V1/StorePeopleRequest.php Validation rules for creating people via API.
app/Http/Requests/Api/V1/StoreOpportunityRequest.php Validation rules for creating opportunities via API.
app/Http/Requests/Api/V1/StoreNoteRequest.php Validation rules for creating notes via API.
app/Http/Requests/Api/V1/StoreCompanyRequest.php Validation rules for creating companies via API.
app/Http/Middleware/SetApiTeamContext.php Resolves/switches team (incl. X-Team-Id) and applies tenant scopes for API/MCP.
app/Http/Controllers/Api/V1/TasksController.php Task CRUD endpoints delegating business logic to Actions.
app/Http/Controllers/Api/V1/PeopleController.php People CRUD endpoints delegating business logic to Actions.
app/Http/Controllers/Api/V1/OpportunitiesController.php Opportunity CRUD endpoints delegating business logic to Actions.
app/Http/Controllers/Api/V1/NotesController.php Note CRUD endpoints delegating business logic to Actions.
app/Http/Controllers/Api/V1/CompaniesController.php Company CRUD endpoints delegating business logic to Actions.
app/Filament/Resources/TaskResource.php Switches Filament edit behavior to use shared UpdateTask Action.
app/Enums/CreationSource.php Adds API creation source enum value for API/MCP created records.
app/Actions/Task/UpdateTask.php Encapsulates task update + assignee notification behavior.
app/Actions/Task/ListTasks.php Encapsulates task listing/filtering/pagination behavior.
app/Actions/Task/DeleteTask.php Encapsulates task deletion behavior with policy enforcement.
app/Actions/Task/CreateTask.php Encapsulates task creation behavior with source/team/creator tracking.
app/Actions/People/UpdatePeople.php Encapsulates people update behavior with policy enforcement.
app/Actions/People/ListPeople.php Encapsulates people listing/filtering/pagination behavior.
app/Actions/People/DeletePeople.php Encapsulates people deletion behavior with policy enforcement.
app/Actions/People/CreatePeople.php Encapsulates people creation behavior with source/team/creator tracking.
app/Actions/Opportunity/UpdateOpportunity.php Encapsulates opportunity update behavior with policy enforcement.
app/Actions/Opportunity/ListOpportunities.php Encapsulates opportunity listing/filtering/pagination behavior.
app/Actions/Opportunity/DeleteOpportunity.php Encapsulates opportunity deletion behavior with policy enforcement.
app/Actions/Opportunity/CreateOpportunity.php Encapsulates opportunity creation behavior with source/team/creator tracking.
app/Actions/Note/UpdateNote.php Encapsulates note update behavior with policy enforcement.
app/Actions/Note/ListNotes.php Encapsulates note listing/filtering/pagination behavior.
app/Actions/Note/DeleteNote.php Encapsulates note deletion behavior with policy enforcement.
app/Actions/Note/CreateNote.php Encapsulates note creation behavior with source/team/creator tracking.
app/Actions/Company/UpdateCompany.php Encapsulates company update behavior with policy enforcement.
app/Actions/Company/ListCompanies.php Encapsulates company listing/filtering/pagination behavior.
app/Actions/Company/DeleteCompany.php Encapsulates company deletion behavior with policy enforcement.
app/Actions/Company/CreateCompany.php Encapsulates company creation behavior with source/team/creator tracking.

…g violation

The dispatchFaviconFetchIfNeeded method loaded customFieldValues but not
the nested customField relationship. When getValue() accessed
customField->type, it triggered a LazyLoadingViolationException in
non-production environments.
…tests

- Fix middleware priority: SetApiTeamContext runs before SubstituteBindings
- Add Gate::authorize() to show() methods for defense-in-depth
- Scope exists validation rules to prevent cross-tenant references
- Remove duplicate throttle middleware from API routes
- Return 201 status for store() endpoints
- Integrate spatie/laravel-query-builder for declarative filtering/sorting
- Add sort column allowlists via allowedSorts()
- Update MCP tools for new List action signatures
- Improve tests with fluent AssertableJson, assertInvalid(), whereType()
- Add missing() assertions to verify no sensitive data leaks
- Add cross-tenant isolation tests for all 5 resources
- Simplify FormatsCustomFields with mapWithKeys()
- Remove dead class_exists guard in UpdateTask
- Remove unnecessary parentheses around new Resource() calls in store methods
- Use explicit instanceof Team check in SetApiTeamContext middleware
- Add return type to arrow function in FormatsCustomFields trait
@ManukMinasyan
Copy link
Copy Markdown
Contributor Author

Note: Custom Fields Write Support (Next Step)

This PR establishes the REST API foundation — CRUD, auth, team scoping, query builder, and tests. Custom fields are readable in responses but not yet writable via create/update endpoints.

Plan for custom fields write support:

  • Pull validation rules from ValidationService::getValidationRules() into API FormRequests (same pattern used by ImportWizard)
  • Only fetch submitted custom field definitions from DB (+ required fields on create)
  • Call $model->saveCustomFields() in Create/Update actions after model persistence
  • Dot-notation error paths (custom_fields.annual_revenue)

Dependency note:

custom-fields#75 refactors the internal validation system (capabilities-based), but the public ValidationService interface (getValidationRules(), isRequired()) remains stable. No need to wait — API integration will work before and after that PR merges.

* feat: align opportunity view actions with people and companies (#138)

Move Edit button outside the dropdown as a standalone action and add
Copy Page URL / Copy Record ID actions inside the dropdown, matching
the existing pattern in ViewPeople and ViewCompany.

* feat: add ValidatesCustomFields trait for API FormRequests

* feat: add custom field validation and write support to API

- Wire ValidatesCustomFields trait into all 10 FormRequests
- Extract and persist custom_fields in all Create/Update actions via TenantContextService
- Change loadMissing to load in controllers for fresh custom field values
- Add tests: create/update with custom fields, validation rejection, unknown fields ignored

* feat: force JSON responses for all API routes

* feat: add cursor pagination support to all list endpoints

* feat: install and configure Scramble for auto-generated API docs

* feat: add sparse fieldsets support to all list endpoints

* refactor: accept explicit perPage parameter in List actions

* feat: add custom fields metadata endpoint (GET /api/v1/custom-fields)

* fix: resolve arch test failures and use correct model imports

* fix: apply rector rules for type hints and return types

* feat: enable API tokens feature in Jetstream

* refactor: simplify MCP endpoint path from /mcp/relaticle to /mcp

* feat: rebuild API tokens page with Filament components

Replace raw Jetstream ApiTokenManager with custom Filament-based
Livewire components matching the Profile page pattern:

- CreateApiToken: form with token name (unique per user), required
  permissions, and modal displaying the new token with copy button
- ManageApiTokens: Filament table with edit permissions and delete
  actions, proper wildcard abilities display
- Fix rate-limited notification using missing translation keys
- Add show() includes support for all 5 API controllers

* style: fix pint formatting in API token components

* fix: update laravel/mcp to v0.5.9 and fix tool pagination

laravel/mcp v0.5.7 was missing the Server\Attributes directory
(Description, Name, Version, etc.), causing tools/list to crash
with "Attribute class not found" when clients tried to discover
tools. Also increased default pagination from 15 to 50 so all
20 CRM tools are returned in a single page.

* feat: migrate API resources to Laravel's JsonApiResource

Adopt JSON:API format for all API responses using Laravel 12's native
JsonApiResource. Resources now use toAttributes() and toRelationships()
instead of toArray(), and controllers no longer need manual include
parsing in show() methods since JsonApiResource handles ?include=
automatically via loadMissing().

* test: update API tests for JSON:API response format

Adapt all assertions to JSON:API structure: attributes nested under
data.attributes, type field at data level, relationships and included
for eager-loaded relations, and dot-notation plucks for collection data.

* refactor: remove redundant foreign keys and add include tests

Remove company_id/contact_id from resource attributes since they are
redundant with declared JSON:API relationships. Clean up stale docblock
on CustomFieldResource. Add include/relationship tests for all entities
with deeper assertions on included entry structure.

* feat: add team-scoped API tokens with expiration

API tokens are now permanently scoped to a specific team at creation
time, following the GitHub/Vercel pattern. This resolves issues where
MCP tools hang because $user->currentTeam is null or wrong for
programmatic clients.

- Add team_id foreign key to personal_access_tokens table
- Add team selector and expiration dropdown to token creation form
- Show team and expiration columns in token management table
- Resolve team from token first in SetApiTeamContext middleware
- Create custom PersonalAccessToken model with team relationship
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 105 out of 108 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

app/Livewire/App/ApiTokens/CreateApiToken.php:1

  • The click handler uses direct JavaScript string interpolation with $wire.plainTextToken, which could be vulnerable to XSS if the token contains unexpected characters. While Sanctum tokens are typically alphanumeric, consider using Livewire's native clipboard action or properly escaping the JavaScript context.
    app/Http/Requests/Api/V1/Concerns/ValidatesCustomFields.php:1
  • The orWhereJsonContains will match any field with a required rule, not scoped to the current where clause context. This should be wrapped in a closure to ensure proper query grouping: $query->where(function($q) use ($submittedCodes) { $q->whereIn('code', $submittedCodes)->orWhereJsonContains(...); })

- Add terminate() to SetApiTeamContext for Octane-safe scope cleanup
- Validate entity_type in CustomFieldsController with Rule::in()
- Decouple UpdateTask from Filament panel context
- Scope ForceJsonResponse middleware to v1 routes only
- Use ??= in observers to prevent overwriting pre-set values
- Add $fillable to PersonalAccessToken, exclude from final_class rule
- Change CreationSource::API badge color from danger to info
@ManukMinasyan ManukMinasyan moved this to In Progress in Roadmap Feb 20, 2026
@ManukMinasyan ManukMinasyan changed the title feat: add REST API and MCP server for CRM entities feat: REST API, MCP server, and team-scoped API tokens Feb 20, 2026
Distinguish MCP-created records from REST API-created records by
introducing a dedicated CreationSource::MCP case. All 5 MCP create
tools now use this value instead of CreationSource::API.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 108 out of 111 changed files in this pull request and generated 9 comments.

Tokens are used for both REST API and MCP server connections.
'Access Tokens' better reflects the broader scope, following
the pattern used by GitHub and Vercel.
Move all user-facing 'API Token' strings to lang/en/access-tokens.php
and rename to 'Access Tokens' throughout. Covers Filament page,
Livewire components, Blade templates, and user menu.
- Clamp per_page to minimum of 1 in all List actions to prevent invalid pagination
- Use Js::from() for clipboard JS string escaping in View pages
- Fix entity_type 'person' to 'people' in CustomFieldsApiTest to match morph map
- Add PersonalAccessToken to arch test ignore list (Sanctum mocks prevent final)
Covers 5 targeted refactors from PR #136 code review:
- Remove redundant Gate::authorize() from controllers
- Move eager-loading into actions
- Create IndexRequest for per_page validation
- Fix FormatsCustomFields return type consistency
The ValidatesCustomFields trait called toRules() with raw request input
during rule construction, before validation could reject non-array types.
Sending custom_fields as a string or integer caused a TypeError crash
(500) instead of a validation error (422).

Guard with is_array() check and change nullable to sometimes.
Layered rate limiting: per-team ceiling (600/min), per-token
reads (300/min) and writes (60/min). Currently the api/mcp
rate limiters are defined but never applied.
6 tasks covering authorization cleanup, eager-loading, IndexRequest,
FormatsCustomFields return type, and layered rate limiting.
…idation, rate limiting

- Remove redundant Gate::authorize() from controllers (actions self-authorize)
- Move customFieldValues eager-loading from controllers into actions
- Create IndexRequest for per_page validation (replaces manual clamping)
- Fix FormatsCustomFields to always return stdClass
- Apply layered rate limiting: 600/min per team, 300/min reads, 60/min writes
- Move throttle middleware after auth:sanctum for token-based keying
Phone, email, and link fields define per-item validation rules
(phone:AUTO, email, url regex) via getItemValidationRules() but
toRules() never included them. Invalid items in arrays passed
validation silently.
…om fields

Replace broken link tests that passed for the wrong reason (field didn't
exist, so "unknown field" rejection matched). New tests create the custom
field first, then verify item-level validation rules reject invalid
values (bad domain, bad email, bad phone) and accept valid ones.
…essTokens

- Eliminate ValidatesCustomFields traits (API + MCP) by consolidating
  custom field rule generation into ValidCustomFields::toRules()
- Rename ApiTokens -> AccessTokens across classes, views, and tests
  to match user-facing "Access Tokens" terminology
- Add @property-read Team|null $currentTeam to User model, removing
  stale @phpstan-ignore and redundant @var annotations
- Remove dead code: redundant casts() in PersonalAccessToken, unused
  navigation properties in AccessTokens page, authorize() in
  ContactRequest, per_page clamping in CustomFieldsController
- Add section on using actions for write operations to ensure single-source-of-truth for business logic and side effects.
- Include notes on Filament CRUD usage and best practices for extracting inline logic.
- Add `laravel-best-practices` skill to guide adherence to Laravel coding patterns and refactoring standards.
…handling

- Sanitize top-level string fields (name, title) in HtmlSanitizer,
  not just custom_fields, preventing stored XSS via API input
- Cache known custom field codes in ValidCustomFields to eliminate
  duplicate DB query on every create/update request
- Replace allUsers()->pluck('id') with direct DB query in Task
  requests to avoid hydrating full User models for ID collection
- Wrap NotifyTaskAssignees in defer() to send DB notifications
  post-response instead of blocking the API caller
- Replace whereHas with whereIn subqueries in User tenant scope
  for better index usage on team membership queries
- Add shouldRenderJsonWhen to bootstrap/app.php so exceptions
  outside middleware pipeline still return JSON for API routes
- Validate team ownership on PersonalAccessToken updating event
  (team_id is set post-creation via fill/save, bypassing creating)
… coverage

- Enforce read ability on MCP resources/prompts via native shouldRegister() hook
- Fix HtmlSanitizer XSS: recurse into nested array values in custom fields
- Add team-scoped Rule::exists() validation to MCP detach tools
- Redact available field list from custom field validation error messages
- Add null guard to TeamScope with whereRaw('1 = 0') to prevent data leaks
- Use $request->safe()->integer() for type-safe validated pagination params
- Make HtmlSanitizer and CustomFieldMerger readonly (fix arch test)
- Fix domains custom field unique constraint in CompaniesApiTest
- Exclude api/* from smoke tests (dedicated API test suite covers them)
- Remove redundant RefreshDatabase uses from 3 test files
- Add mutates() declarations, cursor pagination, PATCH, and unauth tests
…rs in AI agent preview

- Reorder hero tabs: Pipeline > AI Agent > Companies > Custom Fields
- Pipeline is now the default tab on page load
- Add visible MCP tool call indicators (create-people-tool, list-opportunities-tool) to AI agent preview
- Replace generic company names with distinctive ones (Kovra Systems, Meridian Health, Trellis Labs)
- Redesign deal cards as a single unified card with divide-y rows for consistency
- Add contact avatar with colored initials gradient
- Fix animation flash by hiding elements via CSS before JS animation fires
- Refine staggered animation sequence: avatar → tool call → text → card
Clean chat style matching Claude/ChatGPT — user messages are plain text,
only agent responses show the sparkle avatar.
…view

Add "You" and "Relaticle" labels with avatars to clearly show the
conversation between user and AI agent, matching Claude/ChatGPT patterns.
Show ChatGPT as the AI assistant calling relaticle/ MCP tools, making it
clear Relaticle is the tool provider — not a built-in chatbot.
The Symfony HtmlSanitizer was designed for rich HTML content but was
applied to all string attributes including name/title fields. This
caused data corruption: apostrophes became ', ampersands became
&, and script tags produced empty names bypassing validation.

Following Filament's standard approach: store raw data, escape on
output. Blade {{ }} and Filament TextColumn handle XSS protection
at render time. The API returns JSON which clients escape themselves.
Merge origin/main into feat/rest-api-mcp-server, keeping both:
- API middleware priority (SetApiTeamContext) from feature branch
- Guest redirect logic (team invitation flow) from main
Escape backslashes before % and _ to prevent double-escaping
when input contains literal wildcard characters.
@ManukMinasyan ManukMinasyan merged commit 8766d38 into main Mar 29, 2026
10 checks passed
@github-project-automation github-project-automation bot moved this from In Progress to Done in Roadmap Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

2 participants