MCP e Pi Agent

Quando lavoriamo con un coding agent, il primo limite che incontriamo non è quasi mai la capacità del modello di scrivere codice. Il limite vero è il contesto operativo.

Il modello può ragionare, progettare, modificare file, eseguire comandi. Ma quando deve parlare con un sistema esterno — un database, una API aziendale, un gestionale, un motore di ricerca interno, una piattaforma documentale — ha bisogno di un ponte.

È qui che entra in gioco MCP, cioè Model Context Protocol.

MCP è uno standard aperto pensato per collegare applicazioni AI a strumenti, dati e workflow esterni. In pratica permette a un agent come Pi di dire: “ho bisogno di questa informazione” oppure “esegui questa azione”, delegando il lavoro a un server esterno che espone strumenti utilizzabili dal modello.

In questa guida costruiamo un esempio concreto: un piccolo connettore MCP locale che espone alcuni strumenti legati a un ipotetico sito nodeai.it, e poi lo colleghiamo a Pi Agent.


L’idea: un connettore come ponte tra Pi e il nostro mondo

Un connettore MCP può essere visto come un piccolo server che dichiara:

  • quali strumenti mette a disposizione;
  • quali parametri accettano questi strumenti;
  • quale risultato restituiscono;
  • come il client, in questo caso Pi, può chiamarli.

Nel nostro esempio creeremo un server MCP chiamato nodeai-mcp.

Esporrà due tool:

  1. search_articles
    Cerca articoli in una piccola base dati locale.
  2. get_article
    Restituisce il contenuto sintetico di un articolo.

È un esempio volutamente semplice. Ma la stessa logica può essere applicata a un CRM, a un database, a Notion, a WordPress, a un gestionale interno o a una API privata.


Prima di iniziare: cosa serve

Servono:

node -v
npm -v
pi --version

Poi installeremo il pacchetto ufficiale MCP per TypeScript:

npm install @modelcontextprotocol/sdk zod

Il motivo è semplice: la documentazione ufficiale MCP consiglia l’uso di McpServer dal pacchetto @modelcontextprotocol/sdk/server/mcp.js e, per un server locale, il transport StdioServerTransport.

Per Pi useremo invece un’estensione MCP. Una soluzione pratica è:

pi install npm:pi-mcp-adapter

Questa estensione permette a Pi di usare server MCP tramite un file ~/.pi/agent/mcp.json.


1. Creare il progetto MCP

Creiamo una cartella:

mkdir nodeai-mcp
cd nodeai-mcp
npm init -y

Installiamo le dipendenze:

npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx

Aggiungiamo un tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "dist",
    "strict": true
  },
  "include": ["src"]
}

Modifichiamo package.json:

{
  "name": "nodeai-mcp",
  "version": "1.0.0",
  "type": "module",
  "bin": {
    "nodeai-mcp": "./dist/index.js"
  },
  "scripts": {
    "dev": "tsx src/index.ts",
    "build": "tsc",
    "start": "node dist/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0",
    "zod": "^3.23.0"
  },
  "devDependencies": {
    "@types/node": "^22.0.0",
    "tsx": "^4.0.0",
    "typescript": "^5.0.0"
  }
}

2. Scrivere il server MCP

Creiamo il file:

mkdir src
touch src/index.ts

Dentro src/index.ts inseriamo:

#!/usr/bin/env node

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const articles = [
  {
    slug: "react-flow-architetture-complesse",
    title: "React Flow: visualizzare architetture complesse in modo intelligente",
    category: "frontend",
    summary:
      "Un articolo su come trasformare architetture, pipeline e sistemi complessi in mappe interattive navigabili.",
  },
  {
    slug: "graphrag-knowledge-graph",
    title: "GraphRAG e Knowledge Graph nella AI generativa",
    category: "ai",
    summary:
      "Una guida su come i grafi di conoscenza possono migliorare retrieval, contesto e spiegabilità nei sistemi AI.",
  },
  {
    slug: "csv-knowledge-map",
    title: "Da CSV a Knowledge Map",
    category: "data",
    summary:
      "Un esempio pratico per trasformare dati strutturati in mappe della conoscenza navigabili.",
  },
];

const server = new McpServer({
  name: "nodeai-mcp",
  version: "1.0.0",
});

server.registerTool(
  "search_articles",
  {
    title: "Cerca articoli NodeAI",
    description:
      "Cerca articoli nella base dati locale di NodeAI per parola chiave o categoria.",
    inputSchema: {
      query: z.string().describe("Parola chiave da cercare"),
    },
  },
  async ({ query }) => {
    const q = query.toLowerCase();

    const results = articles.filter((article) => {
      return (
        article.title.toLowerCase().includes(q) ||
        article.category.toLowerCase().includes(q) ||
        article.summary.toLowerCase().includes(q)
      );
    });

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(results, null, 2),
        },
      ],
    };
  }
);

server.registerTool(
  "get_article",
  {
    title: "Leggi articolo NodeAI",
    description:
      "Restituisce titolo, categoria e sintesi di un articolo NodeAI dato il suo slug.",
    inputSchema: {
      slug: z.string().describe("Slug dell'articolo da recuperare"),
    },
  },
  async ({ slug }) => {
    const article = articles.find((item) => item.slug === slug);

    if (!article) {
      return {
        content: [
          {
            type: "text",
            text: `Nessun articolo trovato con slug: ${slug}`,
          },
        ],
        isError: true,
      };
    }

    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(article, null, 2),
        },
      ],
    };
  }
);

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);

  // Importante: con stdio, i log devono andare su stderr.
  console.error("nodeai-mcp running on stdio");
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

La parte importante è questa:

const transport = new StdioServerTransport();
await server.connect(transport);

Nel transport stdio, il client avvia il server come processo locale e comunica tramite stdin e stdout usando JSON-RPC. È il metodo più semplice per un connettore locale.


3. Compilare il connettore

Eseguiamo:

npm run build

A questo punto il server compilato sarà in:

dist/index.js

Possiamo provarlo manualmente con:

node dist/index.js

Non vedremo una normale API HTTP, perché questo server parla via stdio. È pensato per essere avviato da un client MCP, non usato direttamente dal browser.


4. Collegare il connettore a Pi Agent

Pi può essere esteso tramite pacchetti ed estensioni TypeScript. Le estensioni possono registrare tool, comandi e comportamenti aggiuntivi.

Per usare MCP, installiamo un adapter:

pi install npm:pi-mcp-adapter

Poi creiamo il file globale:

mkdir -p ~/.pi/agent
nano ~/.pi/agent/mcp.json

Su Windows il percorso equivalente è di solito:

C:\Users\<utente>\.pi\agent\mcp.json

Inseriamo:

{
  "mcpServers": {
    "nodeai": {
      "command": "node",
      "args": [
        "/percorso/assoluto/nodeai-mcp/dist/index.js"
      ],
      "lifecycle": "lazy"
    }
  }
}

Esempio Windows:

{
  "mcpServers": {
    "nodeai": {
      "command": "node",
      "args": [
        "C:/Users/andrea.belvedere/projects/nodeai-mcp/dist/index.js"
      ],
      "lifecycle": "lazy"
    }
  }
}

La modalità lazy significa che il server non viene avviato subito. Pi lo attiva solo quando serve. Questo approccio è utile perché evita di caricare strumenti non necessari all’inizio della sessione.


5. Usare il connettore dentro Pi

Riavviamo Pi:

pi

Ora possiamo chiedere:

Cerca tra gli strumenti MCP qualcosa che possa cercare articoli NodeAI.

Con pi-mcp-adapter, spesso l’uso passa dal tool proxy mcp.

Esempio:

Usa mcp per cercare tool relativi agli articoli.

Oppure:

Cerca articoli NodeAI che parlano di GraphRAG usando il server MCP nodeai.

L’adapter può cercare strumenti disponibili, descriverli e poi chiamarli. Un esempio concettuale è:

mcp({ search: "articles" })

poi:

mcp({
  tool: "nodeai_search_articles",
  args: "{\"query\":\"GraphRAG\"}"
})

Nota importante: in pi-mcp-adapter, gli argomenti dei tool MCP vengono passati come stringa JSON, non come oggetto JavaScript.


6. Variante: esporre i tool direttamente in Pi

Se vogliamo che i tool MCP appaiano direttamente accanto ai tool nativi di Pi, possiamo usare directTools.

Nel file mcp.json:

{
  "mcpServers": {
    "nodeai": {
      "command": "node",
      "args": [
        "C:/Users/andrea.belvedere/projects/nodeai-mcp/dist/index.js"
      ],
      "lifecycle": "lazy",
      "directTools": true
    }
  }
}

Questa scelta è comoda per pochi strumenti. Se invece un server MCP espone decine o centinaia di tool, conviene usare il proxy mcp, perché ogni tool diretto consuma spazio nel prompt di sistema.

È uno dei motivi per cui pi-mcp-adapter nasce con l’idea di un unico tool proxy: ridurre il peso sul contesto.


7. Quando usare stdio e quando HTTP

Per un connettore locale, stdio è spesso la scelta migliore:

  • non serve aprire porte HTTP;
  • il client avvia e chiude il processo;
  • è semplice da distribuire;
  • funziona bene per automazioni locali.

Per un connettore remoto, invece, è meglio usare Streamable HTTP, che la documentazione MCP indica come transport moderno per server remoti.

In sintesi:

Scenario Transport consigliato
Tool locale sul proprio PC stdio
Server interno aziendale streamable-http
API pubblica o cloud streamable-http
Compatibilità vecchi client sse, ma solo se necessario

8. Come trasformare l’esempio in un connettore reale

Il nostro esempio usa un array locale. In un caso reale potremmo sostituirlo con:

  • chiamate REST verso WordPress;
  • query a un database;
  • lettura di file Markdown;
  • interrogazioni a un motore di ricerca interno;
  • chiamate a un CRM;
  • accesso a documentazione tecnica;
  • recupero dati da un knowledge graph.

Ad esempio, il tool search_articles potrebbe chiamare una API:

const response = await fetch(
  `https://www.nodeai.it/wp-json/wp/v2/search?search=${encodeURIComponent(query)}`
);
const data = await response.json();

Il principio rimane lo stesso: MCP non impone come recuperare i dati. Definisce solo il modo standard con cui il modello può chiedere al connettore di fare qualcosa.


Conclusione

MCP diventa interessante quando smettiamo di pensarlo come “un altro protocollo” e iniziamo a vederlo come un livello di integrazione.

Da una parte c’è Pi Agent, con la sua capacità di ragionare, modificare codice e usare strumenti. Dall’altra ci sono i nostri sistemi: dati, API, documenti, servizi, processi. Il connettore MCP sta in mezzo.

Nel piccolo esempio di questa guida abbiamo costruito un server locale, esposto due tool e collegato tutto a Pi tramite un adapter MCP. È un caso semplice, ma contiene già l’architettura completa:

Pi Agent
  ↓
pi-mcp-adapter
  ↓
nodeai-mcp
  ↓
dati, API, documenti, servizi

La parte più importante non è il codice in sé. È il cambio di prospettiva: ogni volta che un sistema esterno può essere descritto come un insieme di strumenti, può diventare parte del workflow di un agent.

Ed è proprio qui che MCP mostra il suo valore: trasformare integrazioni sparse in un linguaggio comune, leggibile sia dalle applicazioni AI sia dagli strumenti che già usiamo ogni giorno.


Fonti

AI Disclaimer

Questo articolo è stato sviluppato a partire dai miei appunti personali, con l’ausilio dell’intelligenza artificiale per riorganizzare i contenuti, rendere più chiara la struttura e trasformare le note iniziali in una guida più leggibile e coerente per tutti.

Di R. Andrea Belvedere

Mi occupo di AI applicata, automazione, LLM, RAG e blockchain, con esperienza in contesti enterprise e industriali. Sono formatore tecnico, scrittore SEO e divulgatore scientifico: mi piace spiegare tecnologie complesse in modo semplice, pratico e orientato all’uso reale. Tutti gli Articoli sono elaborati dal sottoscritto sulla base di un progetto sviluppato direttamente dall’autore. Strumenti di intelligenza artificiale sono stati utilizzati come supporto all’analisi della documentazione e alla redazione. Il contenuto è stato verificato e revisionato dall’autore, che ne assume la responsabilità editoriale.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

errore: Content is protected !!