SAP B1 ServiceLayer MCP Server
Connects AI assistants to SAP Business One ServiceLayer, enabling discovery and query of OData entities, service methods, and safe read-only operations by default, with optional write access and simple npx execution.
README
SAP B1 ServiceLayer MCP Server
Servidor MCP (Model Context Protocol) para conectar asistentes de IA (opencode, Claude, etc.) al ServiceLayer de SAP Business One 10.0, en red local. Ejecutable con npx desde este repositorio de GitHub, sin instalar nada en el PC.
Características
- Solo lectura por defecto: con
SAP_B1_READONLY=true(default) solo se registran tools de consulta (GET). Los tools de escritura (POST/PATCH/DELETE) no existen en el servidor y no pueden llamarse. - Descubrimiento total:
sap_list_entities,sap_get_entity_schemaysap_list_actionsconsultanGET /$metadata(descargado una sola vez por proceso y cacheado) y exponen las ~140 entidades CRUD (incluidas tablas de usuario@y UDOs) y los cientos de métodos de servicio del ServiceLayer. - Modo escritura opcional: con
SAP_B1_READONLY=falsese habilitansap_create,sap_update,sap_deletepara entidades del ServiceLayer ysap_call_actionpara métodos de servicio (pueden tener efectos colaterales). - Ejecución vía
npx github:: sin instalación manual. - Sesión gestionada: login implícito con
CompanyDB/usuario/contraseña, cookiesB1SESSION+ROUTEIDmantenidas en memoria (soporta ServiceLayer multi-nodo), re-login automático ante401y logout garantizado al cerrarse el proceso (además del toolsap_logout). - TLS autofirmado: soporte para certificados autofirmados del ServiceLayer (típico en entornos locales) mediante
SAP_B1_VERIFY_TLS=false. - Sin telemetría ni llamadas externas: el cliente HTTP apunta exclusivamente a la URL configurada (
SAP_B1_SERVER_URL). - Límites de seguridad:
topacotado a 200 registros por consulta.
Tools
Lectura (siempre disponibles)
| Tool | Descripción |
|---|---|
sap_query |
GET genérico a cualquier entidad OData con select, filter, top (≤200), skip, orderby, expand |
sap_list_entities |
Lista todas las entidades OData expuestas por el ServiceLayer (desde $metadata, cacheado); incluye tablas de usuario (@) y UDOs. filter opcional para acotar |
sap_get_entity_schema |
Esquema de una entidad: propiedades (tipos/claves) y navigationProperties (válidas para $expand); resuelve entity sets que comparten EntityType |
sap_list_actions |
Lista los métodos de servicio (function imports, ej: CompanyService_GetCompanyInfo) con sus parámetros |
sap_sql_query |
SQL de solo lectura (SELECT/WITH; INSERT/UPDATE/DELETE/DDL rechazados) vía POST /sql_query — solo en ServiceLayer v2/FP recientes; en v1 antiguos responde error claro |
sap_get_business_partners |
Socios de negocio (clientes/proveedores), filtro por card_type |
sap_get_items |
Artículos del catálogo |
sap_get_sales_orders |
Pedidos de venta; en v1 las líneas (DocumentLines) vienen incluidas sin expand (expand es solo v2) |
sap_get_stock |
Stock de un artículo por ItemCode (+ WarehouseCode opcional); error claro si ItemStock no existe en el ServiceLayer (v1 antiguos) |
sap_session_status |
Estado de la sesión activa |
sap_logout |
Cierre explícito de la sesión |
Escritura (solo si SAP_B1_READONLY=false)
| Tool | Descripción |
|---|---|
sap_create |
Crea un registro en una entidad (POST) |
sap_update |
Actualiza un registro por su clave (PATCH) |
sap_delete |
Elimina un registro por su clave (DELETE) |
sap_call_action |
Invoca un método de servicio (POST); puede tener efectos colaterales (Cancel, UpdateCompanyInfo, Import...) |
Requisitos
- Node.js 18+
- SAP Business One 10.0 con ServiceLayer habilitado (ruta típica
https://<host>:50000/b1s/v1) - opencode (o cualquier cliente MCP)
Configuración (variables de entorno)
| Variable | Requerida | Default | Descripción |
|---|---|---|---|
SAP_B1_SERVER_URL |
Sí | - | URL base del ServiceLayer (ej: https://<host>:50000/b1s/v1) |
SAP_B1_DATABASE |
Sí | - | Nombre de la CompanyDB (ej: SBODEMO_XX) |
SAP_B1_USERNAME |
Sí | - | Usuario del ServiceLayer |
SAP_B1_PASSWORD |
Sí | - | Contraseña del usuario |
SAP_B1_READONLY |
No | true |
false habilita los tools de escritura |
SAP_B1_VERIFY_TLS |
No | true |
false para certificados autofirmados |
SAP_B1_MAX_TOP |
No | 200 |
Límite máximo de top por consulta |
Uso con opencode
En opencode.json del proyecto:
{
"mcp": {
"sap-b1-servicelayer": {
"type": "local",
"command": ["npx", "-y", "github:leonardows1/sap-b1-servicelayer-mcp"],
"environment": {
"SAP_B1_SERVER_URL": "https://<host>:50000/b1s/v1",
"SAP_B1_DATABASE": "<CompanyDB>",
"SAP_B1_USERNAME": "<usuario>",
"SAP_B1_PASSWORD": "<password>",
"SAP_B1_SESSION_TIMEOUT": "30",
"SAP_B1_VERIFY_TLS": "false",
"SAP_B1_READONLY": "true"
},
"enabled": true
}
}
}
Reiniciar opencode después de guardar la configuración.
Seguridad
- Credenciales y cookies de sesión nunca se registran en logs.
- El proceso solo se comunica con
SAP_B1_SERVER_URL. - En modo
READONLY=truelos tools de escritura no se registran: es imposible crear/actualizar/eliminar registros, por diseño. - Configuración validada al arrancar: faltan
SAP_B1_SERVER_URL,SAP_B1_DATABASE,SAP_B1_USERNAMEoSAP_B1_PASSWORD→ el proceso aborta con mensaje claro. - Nombres de entidad validados (
^[A-Za-z][A-Za-z0-9_]*$): no se pueden inyectar rutas (ej:BusinessPartners/...). - Valores de clave y filtros escapados en OData (comillas simples duplicadas): un
idoItemCodecon'no rompe la URL ni el$filter. - La contraseña queda en texto plano en la configuración del cliente MCP. Considerar un secret manager si se comparte el repositorio.
npx github:no tiene versionado semver: cada ejecución toma la última versión del branchmain. Tras actualizar el repo, usarnpm cache clean --forcepara forzar la recarga.
Estructura
Arquitectura hexagonal pragmática (ESM, sin framework): el dominio y los
casos de uso no conocen el transporte MCP ni el HTTP; la infraestructura
implementa el puerto ServiceLayerPort (DIP) y los tools MCP son
controladores delgados.
sap-b1-servicelayer-mcp/
├── package.json # Definición del paquete npm (bin: server.js)
├── server.js # Composition root: cablea dependencias y arranca stdio
├── src/
│ ├── config/
│ │ └── config.js # Configuración desde env, validada e inmutable
│ ├── domain/
│ │ ├── errors.js # Excepciones tipadas (Configuration/InvalidArgument/ServiceLayer)
│ │ ├── oData.js # Helpers puros: query string, filtros, clamp de $top, validación de entidad
│ │ └── edmx.js # Parseo puro de $metadata: entity sets, esquemas, function imports
│ ├── application/
│ │ ├── ports.js # Puerto ServiceLayerPort (contrato, DIP)
│ │ ├── helpers.js # ensureOk / ensureSuccess / unwrapValue
│ │ └── services/
│ │ ├── queryService.js # Consulta GET genérica a entidades OData
│ │ ├── catalogService.js # Socios de negocio y artículos (compone QueryService)
│ │ ├── salesService.js # Pedidos de venta y stock
│ │ ├── sessionService.js # Estado y cierre de sesión
│ │ ├── writeService.js # create / update / delete
│ │ ├── metadataService.js # Descubrimiento: $metadata cacheado, entidades, esquemas y actions
│ │ └── sqlService.js # SQL de solo lectura (SELECT/WITH) vía POST /sql_query
│ └── infrastructure/
│ ├── http/
│ │ ├── httpClient.js # Cliente HTTP mínimo (http/https)
│ │ ├── cookies.js # Manipulación pura de cookies de sesión
│ │ └── serviceLayerClient.js # Adaptador del puerto: login, 401, logout
│ └── mcp/
│ ├── result.js # ok / err / serialize / handle (controladores delgados)
│ └── tools.js # Registro de tools MCP
├── test/ # node:test (sin dependencias externas)
│ ├── config.test.js
│ ├── oData.test.js
│ ├── edmx.test.js # parseo EDMX v3/v4 (entity sets, esquemas, function imports)
│ ├── cookies.test.js
│ ├── client.test.js
│ ├── fakePort.js # fake tipado del puerto ServiceLayerPort (compartido)
│ ├── services.test.js # casos de uso con cliente fake (anti-inyección)
│ ├── metadataService.test.js # descubrimiento y acciones con fake
│ ├── sqlService.test.js # SQL solo-lectura (rechazos, Service Not Found)
│ └── tools.test.js # integración MCP in-memory (registro y llamadas)
├── .gitignore
└── README.md
Adaptación al esquema real (verificado contra ServiceLayer 10.0 v1)
El servidor se adapta dinámicamente al $metadata de cada instancia, sin
nada hardcodeado. Hechos verificados en una instancia real (v1, OData v3):
- Entity sets comparten EntityType:
Orders/Invoices/DeliveryNotes→SAPB1.Document.sap_get_entity_schemaresuelve el tipo real automáticamente. - Líneas de documento: en v1 son complex collections (
DocumentLines,DocumentInstallments) que vienen inline en la respuesta;$expandsolo aplica a navigationProperties (el esquema las lista, ej:BusinessPartner,Currency). - Campos financieros: en v1
BusinessPartnersno tieneBalance; usaCurrentAccountBalance,OpenOrdersBalance,OpenDeliveryNotesBalance. Las facturas no tienenBalanceDue: el saldo abierto esDocTotal − PaidToDate. - Sin
ItemStockni/sql_queryen v1 antiguos:sap_get_stockavisa con entidades de stock reales descubiertas;sap_sql_querydevuelve error claro. - Function imports v3 con
IsBindable="true"se listan comobound(no invocables standalone) para no contaminarsap_list_actions.
Receta: reporte de antigüedad de saldos (30/60/90)
Sin SQL, solo con sap_query (funciona en cualquier v1/v2):
- Facturas abiertas (paginar con
skipen lotes ≤200 si hay muchas):sap_query('Invoices', filter='PaidToDate lt DocTotal', select='CardCode,CardName,DocNum,DocDate,DocDueDate,DocTotal,PaidToDate,DocumentStatus,ControlAccount') - Por cada factura:
saldo = DocTotal − PaidToDate;días = hoy − DocDueDate. - Agrupar por rangos 0-30 / 31-60 / 61-90 / 90+ y por cliente (o por
ControlAccountpara la vista por cuenta contable). - Totales por cliente/cuenta:
sap_get_business_partnersconCurrentAccountBalance(saldo actual) yCreditLimit.
Con sap_sql_query (v2) el mismo reporte es una sola query sobre
OINV/OINV3/OFRJ/OCRD.
Desarrollo
npm install # dependencias
npm test # tests (node:test)
npm run typecheck # verificación de tipos estricta (tsc --noEmit sobre JSDoc)
npm start # arranque local (requiere variables de entorno)
Todo el código JS está verificado con TypeScript estricto vía JSDoc
(checkJs + strict + noUncheckedIndexedAccess): tsconfig.json sin
build step, el servidor se ejecuta directo con node.
Verificación manual (JSON-RPC por stdio)
echo '{"jsonrpc":"2.0","method":"tools/list","id":1}' | \
SAP_B1_SERVER_URL=... SAP_B1_DATABASE=... SAP_B1_USERNAME=... SAP_B1_PASSWORD=... \
npx -y github:leonardows1/sap-b1-servicelayer-mcp
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.