Espone API JSON e MCP HTTP per far lavorare gli agenti sul CRM.

Gli agenti autenticati con token Bearer possono leggere today/pipeline e annotare attività, con host MCP allineati a quelli di produzione.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-02 22:43:46 +02:00
co-authored by Cursor
parent 1f946f63ba
commit 936930e691
44 changed files with 1693 additions and 4 deletions
+1
View File
@@ -7,5 +7,6 @@ class AdminController < ApplicationController
@active_users_count = User.active.count
@projects_count = Project.count
@mail_identities_count = MailIdentity.count
@api_tokens_count = current_user.api_tokens.active.count
end
end
+28
View File
@@ -0,0 +1,28 @@
module Api
class BaseController < ActionController::API
wrap_parameters false
before_action :authenticate_api_token!
attr_reader :current_user, :current_api_token
private
def authenticate_api_token!
token = ApiToken.authenticate(bearer_token)
unless token
render json: { error: "Non autenticato" }, status: :unauthorized
return
end
@current_api_token = token
@current_user = token.user
Current.user = @current_user
token.touch_last_used!
end
def bearer_token
header = request.authorization.to_s.presence || request.headers["Authorization"].to_s
header[/Bearer\s+(.+)/i, 1]
end
end
end
@@ -0,0 +1,16 @@
module Api
module V1
class ActivitiesController < BaseController
def create
render_agent Crm::AgentSession.new(current_user).create_activity(params[:project_code], activity_params)
end
private
def activity_params
source = params[:activity].presence || params
source.permit(:organization_id, :activity_type, :subject, :description, :happened_at, :contact_id, :opportunity_id, :lost_reason)
end
end
end
end
+11
View File
@@ -0,0 +1,11 @@
module Api
module V1
class BaseController < Api::BaseController
private
def render_agent(result)
render json: result.body, status: result.status
end
end
end
end
@@ -0,0 +1,16 @@
module Api
module V1
class OpportunitiesController < BaseController
def update_stage
source = params[:opportunity].presence || params
render_agent Crm::AgentSession.new(current_user).update_opportunity_stage(
params[:project_code],
params[:id],
pipeline_stage: source[:pipeline_stage],
lost_reason: source[:lost_reason],
notes: source[:notes]
)
end
end
end
end
@@ -0,0 +1,9 @@
module Api
module V1
class OrganizationsController < BaseController
def show
render_agent Crm::AgentSession.new(current_user).organization(params[:project_code], params[:id])
end
end
end
end
@@ -0,0 +1,9 @@
module Api
module V1
class ProjectsController < BaseController
def index
render_agent Crm::AgentSession.new(current_user).projects
end
end
end
end
@@ -0,0 +1,9 @@
module Api
module V1
class SearchController < BaseController
def show
render_agent Crm::AgentSession.new(current_user).search(params[:project_code], params[:q])
end
end
end
end
@@ -0,0 +1,23 @@
module Api
module V1
class TasksController < BaseController
def create
render_agent Crm::AgentSession.new(current_user).create_task(params[:project_code], task_params)
end
def complete
render_agent Crm::AgentSession.new(current_user).complete_task(params[:project_code], params[:id])
end
private
def task_params
source = params[:task].presence || params
source.permit(
:title, :description, :organization_id, :contact_id, :opportunity_id,
:assigned_user_id, :due_at, :priority, :task_type
)
end
end
end
end
@@ -0,0 +1,9 @@
module Api
module V1
class TodayController < BaseController
def show
render_agent Crm::AgentSession.new(current_user).today(params[:project_code])
end
end
end
end
+26
View File
@@ -0,0 +1,26 @@
class ApiTokensController < ApplicationController
before_action :set_api_token, only: :destroy
def index
@page_title = "Token API"
@api_tokens = current_user.api_tokens.active.order(created_at: :desc)
@revealed_token = session.delete(:revealed_api_token)
end
def create
token = ApiToken.issue!(user: current_user, name: params[:name])
session[:revealed_api_token] = token.plaintext
redirect_to api_tokens_path, notice: "Token creato. Copialo ora: non sarà più visibile."
end
def destroy
@api_token.revoke!
redirect_to api_tokens_path, notice: "Token revocato."
end
private
def set_api_token
@api_token = current_user.api_tokens.active.find(params[:id])
end
end
+20
View File
@@ -0,0 +1,20 @@
class McpController < Api::BaseController
def handle
request.body.rewind if request.body.respond_to?(:rewind)
server = EminuxCrmMcp.server_for(user: current_user)
transport = MCP::Server::Transports::StreamableHTTPTransport.new(
server,
stateless: true,
serve_subscriptions_listen: false,
enable_json_response: true,
allowed_hosts: EminuxCrmMcp.allowed_hosts,
dns_rebinding_protection: !Rails.env.test?
)
status, headers, body = transport.handle_request(request)
headers.each { |key, value| response.set_header(key, value) }
self.status = status
self.response_body = body
end
end