npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@memori.ai/mcp-crmincloud

v2.2.0

Published

MCP server for TeamSystem CRM in Cloud: companies, opportunities with products, quotes with discounts and PDF download links, live product catalog and read-only activities, with self-verified writes.

Readme

MCP TeamSystem CRM in Cloud

Server MCP per la parte commerciale di TeamSystem CRM in Cloud — anagrafiche, opportunità con i loro prodotti, preventivi (con sconti, revisioni e stampa PDF), catalogo, attività in sola lettura — pensato per essere collegato ad agenti AI tramite un MCP Gateway (es. AIsuru), ma generico: nessun dato specifico di un tenant è hardcoded nel codice, quindi funziona con qualunque azienda su CRM in Cloud.

Pacchetto npm: @memori.ai/mcp-crmincloud · avvio: npx -y @memori.ai/mcp-crmincloud

Il pacchetto pubblicato contiene solo src/: è tutto ciò che serve per far funzionare il server. Test, collaudo e documentazione tecnica stanno fuori dal pacchetto — vedi l'ultima sezione.


🧰 I tool esposti (22)

| Gruppo | Tool | Cosa fa | |---|---|---| | Consultazione | opzioni_opportunita | Stati, fasi, categorie, motivi di perdita (fasi/categorie/motivi letti dal vivo) | | Consultazione | opzioni_preventivo | Stadi, pagamenti, modalità di pagamento, unità di misura e template di stampa, dal vivo | | Anagrafiche | cerca_azienda | Per nome, partita IVA e/o città, verificando che il filtro sia stato applicato | | Anagrafiche | leggi_azienda | Scheda, contatti collegati, opportunità collegate | | Anagrafiche | crea_azienda | Ragione sociale, P.IVA e indirizzo, controllo duplicati · dry_run | | Anagrafiche | aggiorna_azienda | Aggiornamento parziale di ragione sociale, CF, P.IVA e indirizzo completo, con rilettura · dry_run | | Opportunità | cerca_opportunita | Per stato, nome, azienda, fase, intervalli di data | | Opportunità | leggi_opportunita | Dossier completo: dati, azienda, prodotti, preventivi e attività collegati | | Opportunità | crea_opportunita | Crea un'opportunità · dry_run | | Opportunità | aggiorna_opportunita | Aggiorna uno o più campi (titolo, stato, fase, categoria, commerciale…) · dry_run | | Opportunità | prodotti_opportunita | Elenca i prodotti collegati all'opportunità | | Opportunità | salva_prodotto_opportunita | Aggiunge o modifica un prodotto sull'opportunità · dry_run | | Opportunità | elimina_prodotto_opportunita | Rimuove un prodotto dall'opportunità, verificando la cancellazione | | Preventivi | cerca_preventivo | Per azienda, opportunità, numero, stadio | | Preventivi | leggi_preventivo | Numero, stadio, totali, righe (con sconti e unità di misura) | | Preventivi | crea_preventivo | ⭐ Crea un preventivo con le sue righe, o partendo dai prodotti dell'opportunità · dry_run | | Preventivi | aggiorna_preventivo | Aggiorna testata e/o sostituisce le righe · dry_run | | Preventivi | clona_preventivo | Duplica (copia) o crea una revisione (revisione) · dry_run | | Preventivi | scarica_preventivo | Link PDF firmato, verificato scaricandone i primi byte | | Prodotti | cerca_prodotto | Cerca nel catalogo letto dal vivo dal tuo tenant | | Prodotti | leggi_prodotto | Scheda di un singolo prodotto | | Attività | cerca_attivita | Sola lettura, per opportunità e/o azienda |

Il giro completo che un agente può fare da zero: crea_aziendacrea_opportunitasalva_prodotto_opportunitacrea_preventivo (daProdottiOpportunita: true) → scarica_preventivo. Ogni risposta include anche il link alla scheda sul CRM, così l'utente può aprire e controllare quello che l'agente ha fatto — ma quel link lo calcola il server (§ sotto), non lo restituisce l'API.

Comportamenti garantiti dal server:

  • Ogni scrittura si rilegge e si confronta. L'API di CRM in Cloud risponde 200 OK e può scartare campi in silenzio: i campi persi finiscono in campiPersi, e il tool non dichiara successo pieno se la lista non è vuota.
  • Le righe si validano prima di qualunque chiamata HTTP, e gli errori vengono segnalati tutti insieme invece che uno alla volta.
  • Mai indovinare un'unità di misura. Le unità si leggono dal vivo dal CRM: se un prodotto non ne dichiara una a catalogo, il tool si ferma e chiede quale usare invece di scegliere.
  • Mai dedurre un prezzo. I prezzi a catalogo sono quasi tutti a zero.
  • Le aziende e le opportunità si individuano per id, mai per nome: le ricerche per nome vengono verificate rileggendo i risultati, perché l'API a volte ignora in silenzio un filtro sbagliato e risponde comunque 200 con una lista qualsiasi.
  • Gli aggiornamenti inviano solo i campi passati: il CRM fa un merge parziale vero su un record esistente, i campi non richiesti restano intatti. La scrittura viene comunque riletta per intero, per accorgersi di qualunque effetto collaterale (es. chiudere un'opportunità aggiorna anche closeDate a oggi: atteso, segnalato in nota).
  • Il rate limit (40 chiamate/min) è condiviso fra processi, con una finestra scorrevole persistita su file: più agenti attivi insieme non lo sforano.
  • Nessun dato di un tenant specifico è nel codice. Fasi, categorie, motivi di perdita, pagamenti, modalità di pagamento e catalogo prodotti si leggono dal vivo dal CRM di chi usa il server (cache locale di pochi minuti/un'ora, mai spedita nel pacchetto).

📇 Catalogo, unità di misura e prezzi

cerca_prodotto/crea_preventivo leggono esistenza, nome, prezzo, categoria, aliquota e unità dichiarata dei prodotti dal vivo dal catalogo del tuo tenant (Catalog/Search, cache di 10 minuti), e le unità di misura dall'anagrafica del CRM (AlternativeUnit/Search): tutto questo funziona da subito, per qualunque tenant, senza configurazione né file di dati.

Nelle righe l'unità si passa per nome (unitaMisura: "mesi") oppure per id, e viene risolta contro l'elenco vero: un nome inesistente viene rifiutato elencando quelli validi. Se il prodotto ne dichiara una a catalogo, quella è il default.

Esiste anche uno storico locale opzionale (out/contratto-prodotti.json, generabile con uno script in others/, mai committato perché contiene prezzi e quantità reali): serve solo a recuperare tipo riga e aliquota per prodotti mai usati, e il server funziona identico senza.


💸 Sconti, note e commerciale

  • Sconto su un prodotto specifico: scontoFormula sulla riga, es. "10" oppure "10+5" per sconti in cascata. prezzoUnitario resta il prezzo pieno e il netto lo calcola il CRM, così la stampa mostra listino, sconto e netto. Se il prodotto ha uno sconto massimo a catalogo, superarlo è un errore. (Passare un listino diverso dal prezzo senza formula viene rifiutato: il CRM lo annullerebbe in silenzio.)
  • Sconto sul totale: una riga senza codice prodotto, con la sola descrizione e prezzoUnitario negativo. Eredita l'aliquota IVA delle righe prodotto, altrimenti scontarebbe l'IVA sbagliata.
  • Note del preventivo: una riga con testo (HTML ammesso), che diventa una riga puramente descrittiva senza prezzo.
  • Commerciale: per default è l'utente della CRM_API_KEY, ma è sempre modificabilecommerciale (email o id utente) su crea_azienda, crea_opportunita, aggiorna_opportunita, crea_preventivo e aggiorna_preventivo.

🔗 I link alle schede: calcolati, non forniti dall'API

L'API di CRM in Cloud non restituisce le URL dell'interfaccia web, e quelle URL non usano l'id numerico ma un id offuscato (/opportunities/8Xlw678O). I link nelle risposte li calcola questo server dall'id, riproducendo lo schema del frontend React attuale: hashids con salt hkjt753gynbv e lunghezza minima 8. La configurazione è stata letta direttamente dal RouteHelper del bundle React e verificata sulla coppia reale opportunità 7109188Xlw678O.

La forma della rotta dipende dall'entità: le opportunità usano /opportunities/{hash}, le aziende /companies/{hash}/company, mentre i preventivi — che nel frontend non hanno una modalità di sola vista — usano /quotes/edit/{hash}/quote.

Siccome è uno schema letto dall'interfaccia e non un contratto pubblico, ogni risposta che contiene link porta anche un campo linkNota che lo dichiara: se un link non apre la scheda giusta, il riferimento certo è l'id numerico, che c'è sempre. In quel caso:

CRM_LINK_SALT=...        # solo se l'interfaccia usa uno schema diverso dal default
CRM_LINK_MIN_LENGTH=8
CRM_LINKS=off            # oppure niente link: restano gli id

🔌 Configurazione nell'MCP Gateway

Il gateway esegue il server come sottoprocesso e gli inietta i parametri come variabili d'ambiente: il nome del parametro diventa letteralmente il nome della variabile. Le credenziali non passano mai dall'LLM.

Tipo server: Server MCP Generico (generic) · Comando: npx -y @memori.ai/mcp-crmincloud

| Parametro | Obbligatorio | Cosa | |---|---|---| | CRM_API_KEY | sì | Web API Key (SETUP → USERS → utente → Web API Keys). Non scade. Determina l'utente proprietario dei record creati | | CRM_BASE_URL | no | Default https://api.crmincloud.it/api/v1. Non usare /api/latest | | CRM_RATE_LIMIT | no | Chiamate al minuto. Default 33, il limite reale del CRM è 40 | | CRM_APP_URL | no | Base dei link alle schede. Default https://app.crmincloud.it | | CRM_LINKS | no | off per non generare link alle schede (§ sotto) | | CRM_LINK_SALT · CRM_LINK_MIN_LENGTH | no | Schema degli id nelle URL dell'interfaccia. Default: salt hkjt753gynbv, lunghezza 8 |

Autenticazione: un solo service account condiviso. Il gateway inietta le credenziali per istanza del server, non per sessione utente: quindi CRM_API_KEY è la Web API Key di un unico utente "tecnico", e i record creati dall'MCP nascono con quell'utente come ownerId/salesPerson. Il commerciale però si può passare esplicitamente su ogni scrittura (parametro commerciale), quindi l'attribuzione corretta non richiede più un'istanza per persona.

Il perimetro di ciò che l'MCP può toccare è quello della chiave: la Web API Key si può limitare per operazione (SETUP → USERS → Web API Keys), ed è lì che va impostato un eventuale vincolo, non nel server.

Nota sul rate limit. Il gateway crea e uccide un processo a ogni chiamata di tool, quindi un freno tenuto in memoria non conterebbe nulla. La finestra scorrevole è persistita su file temporaneo, condivisa fra tutti i processi che usano la stessa chiave. Se il budget è esaurito il tool fallisce subito con un messaggio chiaro, invece di restare appeso finché il gateway lo uccide.


🗂 Struttura di questo repository

| Percorso | Cosa | Serve per far girare il server? | |---|---|---| | src/ | Il server MCP vero e proprio (i 22 tool, client HTTP, catalogo, lookup, link) | sì — assieme a package.json è l'unica cosa pubblicata su npm | | package.json / package-lock.json | Metadati e dipendenze npm | sì | | out/ | Cache e storico opzionale, generati a runtime, mai committati né pubblicati | no |

Il pacchetto pubblicato è autosufficiente: npx -y @memori.ai/mcp-crmincloud scarica src/ più le tre dipendenze e parte. Nessun file di test, di documentazione o di dati serve in produzione.


🧪 Sviluppo, test e documentazione tecnica

Tutto ciò che serve per sviluppare, testare e collaudare il server (non per farlo funzionare) sta fuori dal pacchetto, in una cartella others/ separata:

  • docs/ — documentazione tecnica: riferimento API misurato, insidie note dell'API CRM in Cloud, specifica dei tool, come si dimostra che il collaudo funziona
  • prova-mcp.mjs — test suite: protocollo, schema dei tool, guardie sui parametri (con --rete aggiunge letture dal vivo, senza scrivere niente)
  • collaudo.mjs, riverifica.mjs — collaudo end-to-end contro un CRM reale: ricrea preventivi storici e li confronta byte per byte, più il giro commerciale completo
  • genera-contratto.mjs, congela-fixture.mjs, crea-azienda-test.mjs, verifica-duplicati.mjs, crm-client.mjs, percorsi.mjs — script di supporto
  • .env.example — configurazione per gli script sopra (copiala in .env)
cd others
cp .env.example .env      # poi compila CRM_API_KEY
node prova-mcp.mjs        # nessuna chiamata al CRM
node prova-mcp.mjs --rete # aggiunge letture/ricerche dal vivo
node collaudo.mjs         # giro commerciale completo, scrive su un'azienda di test

Gli script trovano il pacchetto da soli, sia che others/ stia dentro la cartella dell'MCP sia che stia accanto. Se lo tieni in un posto qualsiasi, indicaglielo con MCP_DIR:

MCP_DIR="/percorso/della/cartella/MCP CRMinCloud" node collaudo.mjs