hoyalabmcp 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/build/hoyalab-client.js +145 -0
- package/build/index.js +441 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# HoyaLab MCP Server
|
|
2
|
+
|
|
3
|
+
Servidor de integração oficial HoyaLab para Model Context Protocol (MCP). Este servidor permite que assistentes de IA (como Claude Desktop) gerenciem pedidos, produtos e especificações técnicas diretamente na plataforma HoyaLab.
|
|
4
|
+
|
|
5
|
+
## 🚀 Funcionalidades Principais
|
|
6
|
+
|
|
7
|
+
* **Autenticação Dinâmica**: Login seguro via Telefone e Código do Cliente para obter chaves de acesso específicas da sessão.
|
|
8
|
+
* **Gestão de Pedidos**:
|
|
9
|
+
* Criação de pedidos completos (Surfaçados, Prontos, VTA, Tracer).
|
|
10
|
+
* Suporte técnico avançado: Prismas, Afinamento Prismático, Equilíbrio de Lente e Campos Complementares.
|
|
11
|
+
* Rastreamento em tempo real (Tracking) por número ou data.
|
|
12
|
+
* Download de XML de pedidos.
|
|
13
|
+
* **Catálogo de Produtos**: Consulta de materiais, desenhos, tratamentos, alturas, fotossensíveis e colorações.
|
|
14
|
+
* **Integração de Medidas**: Suporte a medidas VisuReal / Master.
|
|
15
|
+
|
|
16
|
+
## 🛠️ Configuração no Claude Desktop
|
|
17
|
+
|
|
18
|
+
Para utilizar este servidor, adicione a seguinte configuração ao seu arquivo `claude_desktop_config.json`:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"mcpServers": {
|
|
23
|
+
"hoyalab": {
|
|
24
|
+
"command": "npx",
|
|
25
|
+
"args": ["-y", "hoyalabmcp"],
|
|
26
|
+
"env": {
|
|
27
|
+
"HOYALAB_MASTER_KEY": "SUA_CHAVE_MESTRA_AQUI",
|
|
28
|
+
"HOYALAB_ENV": "prod"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Variáveis de Ambiente Necessárias:
|
|
36
|
+
* `HOYALAB_MASTER_KEY`: Chave mestre fornecida pela Hoya para autenticação inicial de clientes.
|
|
37
|
+
* `HOYALAB_ENV`: Define o ambiente (`prod` para produção ou `homolog` para testes).
|
|
38
|
+
|
|
39
|
+
## 🔐 Segurança e Autenticação
|
|
40
|
+
|
|
41
|
+
Este MCP utiliza um fluxo de segurança em duas camadas:
|
|
42
|
+
|
|
43
|
+
1. **Chave Mestra**: Utilizada exclusivamente nos bastidores para validar o acesso ao serviço.
|
|
44
|
+
2. **Sessão do Usuário**: O assistente deve sempre chamar a ferramenta `authenticate` com o **Telefone** e **Código do Cliente** cadastrados.
|
|
45
|
+
3. **Expiração**: Os acessos expiram periodicamente. Se o acesso for negado, o MCP fornecerá automaticamente o link para atualização cadastral no portal HoyaLab.
|
|
46
|
+
|
|
47
|
+
## 📖 Como Usar (Exemplos de Prompts)
|
|
48
|
+
|
|
49
|
+
* *"Listar meus pedidos realizados entre ontem e hoje."*
|
|
50
|
+
* *"Consultar o status do pedido 17141312."*
|
|
51
|
+
* *"Crie um novo pedido com os seguintes dados: OS PED-123, Cliente 49981, Produto 1956..."*
|
|
52
|
+
* *"Quais são os tipos de armação disponíveis?"*
|
|
53
|
+
|
|
54
|
+
## 📝 Suporte Técnico
|
|
55
|
+
|
|
56
|
+
Caso ocorra erro de autenticação, o assistente guiará o usuário para o portal oficial:
|
|
57
|
+
* **Homologação**: [Verificar Cadastro](https://tst.hoyalabhomologa.com.br/HoyaIntegracao/Cliente/visualizarCadastroCliente)
|
|
58
|
+
* **Produção**: [Verificar Cadastro](https://hoyalab.com.br/HoyaIntegracao/Cliente/visualizarCadastroCliente)
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
© 2026 HOYA Brasil. Todos os direitos reservados.
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
export class HoyaLabClient {
|
|
3
|
+
masterKey;
|
|
4
|
+
registrationUrl;
|
|
5
|
+
axiosInstance;
|
|
6
|
+
constructor(apiKey, baseUrl = 'https://hoyalab.com.br/api/customer', masterKey, registrationUrl = 'https://hoyalab.com.br/HoyaIntegracao/Cliente/visualizarCadastroCliente') {
|
|
7
|
+
this.masterKey = masterKey;
|
|
8
|
+
this.registrationUrl = registrationUrl;
|
|
9
|
+
this.axiosInstance = axios.create({
|
|
10
|
+
baseURL: baseUrl,
|
|
11
|
+
headers: {
|
|
12
|
+
'x-api-key': apiKey,
|
|
13
|
+
'Content-Type': 'application/json',
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
// Interceptor para validar proativamente a existência da API Key
|
|
17
|
+
this.axiosInstance.interceptors.request.use((config) => {
|
|
18
|
+
const currentApiKey = config.headers['x-api-key'];
|
|
19
|
+
if (!currentApiKey || currentApiKey === '') {
|
|
20
|
+
const authError = new Error(`ACESSO NEGADO: Autenticação necessária. Por favor, utilize a ferramenta "authenticate" com seu Telefone e Código do Cliente. Caso não consiga autenticar, verifique seu cadastro em: ${this.registrationUrl}`);
|
|
21
|
+
authError.isAuthError = true;
|
|
22
|
+
throw authError;
|
|
23
|
+
}
|
|
24
|
+
return config;
|
|
25
|
+
});
|
|
26
|
+
// Interceptor de resposta para lidar com EXPIRAÇÃO (Erro 401)
|
|
27
|
+
this.axiosInstance.interceptors.response.use((response) => response, (error) => {
|
|
28
|
+
if (error.response && (error.response.status === 401 || error.response.status === 403)) {
|
|
29
|
+
// Limpa a chave expirada/inválida
|
|
30
|
+
this.axiosInstance.defaults.headers['x-api-key'] = '';
|
|
31
|
+
console.error('[Auth] A sessão expirou ou a chave é inválida. Nova autenticação necessária.');
|
|
32
|
+
const sessionError = new Error('Sua sessão expirou ou você não tem permissão para esta ação. Por favor, realize a autenticação novamente usando a ferramenta "authenticate".');
|
|
33
|
+
sessionError.isAuthError = true;
|
|
34
|
+
throw sessionError;
|
|
35
|
+
}
|
|
36
|
+
return Promise.reject(error);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// Orders
|
|
40
|
+
async createOrder(data) {
|
|
41
|
+
const response = await this.axiosInstance.post('/pedido', data);
|
|
42
|
+
return response.data;
|
|
43
|
+
}
|
|
44
|
+
async trackOrder(data) {
|
|
45
|
+
if (!data || Object.keys(data).length === 0) {
|
|
46
|
+
throw new Error('Parâmetros de busca vazios. Por favor, forneça pelo menos um filtro (data ou status).');
|
|
47
|
+
}
|
|
48
|
+
const response = await this.axiosInstance.post('/pedido/tracking', data);
|
|
49
|
+
return response.data;
|
|
50
|
+
}
|
|
51
|
+
async getOrder(numeroPedido) {
|
|
52
|
+
const response = await this.axiosInstance.get(`/pedido/tracking/${numeroPedido}`);
|
|
53
|
+
return response.data;
|
|
54
|
+
}
|
|
55
|
+
async getOrderXml(numeroPedido) {
|
|
56
|
+
const response = await this.axiosInstance.get(`/pedido/xml/${numeroPedido}`);
|
|
57
|
+
return response.data;
|
|
58
|
+
}
|
|
59
|
+
async getPaymentConditions(data) {
|
|
60
|
+
const response = await this.axiosInstance.post('/condicao-pagamento', data);
|
|
61
|
+
return response.data;
|
|
62
|
+
}
|
|
63
|
+
// Products
|
|
64
|
+
async listProducts() {
|
|
65
|
+
const response = await this.axiosInstance.get('/produto');
|
|
66
|
+
return response.data;
|
|
67
|
+
}
|
|
68
|
+
async getProductByCode(codigo) {
|
|
69
|
+
const response = await this.axiosInstance.get(`/produto/${codigo}`);
|
|
70
|
+
return response.data;
|
|
71
|
+
}
|
|
72
|
+
async getProductBySku(sku) {
|
|
73
|
+
const response = await this.axiosInstance.get(`/produto/sku/${sku}`);
|
|
74
|
+
return response.data;
|
|
75
|
+
}
|
|
76
|
+
async getProductColors(data) {
|
|
77
|
+
const response = await this.axiosInstance.post('/coloracao', data);
|
|
78
|
+
return response.data;
|
|
79
|
+
}
|
|
80
|
+
async getFrameTypes() {
|
|
81
|
+
const response = await this.axiosInstance.get('/tipo-armacao');
|
|
82
|
+
return response.data;
|
|
83
|
+
}
|
|
84
|
+
async getDesigns() {
|
|
85
|
+
const response = await this.axiosInstance.get('/desenho');
|
|
86
|
+
return response.data;
|
|
87
|
+
}
|
|
88
|
+
async getMaterials() {
|
|
89
|
+
const response = await this.axiosInstance.get('/material');
|
|
90
|
+
return response.data;
|
|
91
|
+
}
|
|
92
|
+
async getPhotosensitiveTypes() {
|
|
93
|
+
const response = await this.axiosInstance.get('/fotossensivel');
|
|
94
|
+
return response.data;
|
|
95
|
+
}
|
|
96
|
+
async getTreatments() {
|
|
97
|
+
const response = await this.axiosInstance.get('/tratamento');
|
|
98
|
+
return response.data;
|
|
99
|
+
}
|
|
100
|
+
async getHeights() {
|
|
101
|
+
const response = await this.axiosInstance.get('/altura');
|
|
102
|
+
return response.data;
|
|
103
|
+
}
|
|
104
|
+
// VisuReal
|
|
105
|
+
async getVisuRealMeasure(data) {
|
|
106
|
+
const response = await this.axiosInstance.post('/visureal', data);
|
|
107
|
+
return response.data;
|
|
108
|
+
}
|
|
109
|
+
// Auth
|
|
110
|
+
async authenticate(telefone, codigoCliente) {
|
|
111
|
+
if (!this.masterKey) {
|
|
112
|
+
throw new Error('Chave mestre (MASTER_KEY) não configurada no servidor MCP.');
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
// Faz o login usando a chave mestre para obter a chave do cliente
|
|
116
|
+
const response = await axios.post(`${this.axiosInstance.defaults.baseURL}/auth/login-mcp`, {
|
|
117
|
+
codigoCliente,
|
|
118
|
+
telefone
|
|
119
|
+
}, {
|
|
120
|
+
headers: {
|
|
121
|
+
'x-api-key': this.masterKey,
|
|
122
|
+
'Content-Type': 'application/json'
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
const { apiKey, message } = response.data;
|
|
126
|
+
if (apiKey) {
|
|
127
|
+
this.updateApiKey(apiKey);
|
|
128
|
+
return {
|
|
129
|
+
success: true,
|
|
130
|
+
message: message || 'Autenticação realizada com sucesso.'
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
throw new Error(message || 'Não foi possível obter a chave do cliente.');
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
const errorMsg = error.response?.data?.mensagem || error.message;
|
|
137
|
+
throw new Error(`Erro na autenticação: ${errorMsg}. Verifique seus dados ou atualize o cadastro em: ${this.registrationUrl}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
updateApiKey(apiKey) {
|
|
141
|
+
this.axiosInstance.defaults.headers['x-api-key'] = apiKey;
|
|
142
|
+
// Também garantimos que chamadas futuras peguem esse valor explicitamente se necessário
|
|
143
|
+
console.error(`[Auth] API Key atualizada para a sessão.`);
|
|
144
|
+
}
|
|
145
|
+
}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { HoyaLabClient } from './hoyalab-client.js';
|
|
6
|
+
const API_KEY = process.env.HOYALAB_API_KEY || '';
|
|
7
|
+
const MASTER_KEY = process.env.HOYALAB_MASTER_KEY || '';
|
|
8
|
+
const ENV = process.env.HOYALAB_ENV || 'prod';
|
|
9
|
+
const BASE_URLS = {
|
|
10
|
+
prod: 'https://hoyalab.com.br/api/customer',
|
|
11
|
+
homolog: 'https://tst.hoyalabhomologa.com.br/api/customer',
|
|
12
|
+
};
|
|
13
|
+
const REGISTRATION_URLS = {
|
|
14
|
+
prod: 'https://hoyalab.com.br/HoyaIntegracao/Cliente/visualizarCadastroCliente',
|
|
15
|
+
homolog: 'https://tst.hoyalabhomologa.com.br/HoyaIntegracao/Cliente/visualizarCadastroCliente',
|
|
16
|
+
};
|
|
17
|
+
const BASE_URL = BASE_URLS[ENV] || BASE_URLS.prod;
|
|
18
|
+
if (!API_KEY) {
|
|
19
|
+
console.error('Warning: HOYALAB_API_KEY not found. Some tools might require authentication via "authenticate" tool.');
|
|
20
|
+
}
|
|
21
|
+
console.error(`Starting HoyaLab MCP server in ${ENV} mode (${BASE_URL})`);
|
|
22
|
+
class HoyaLabServer {
|
|
23
|
+
server;
|
|
24
|
+
client;
|
|
25
|
+
constructor() {
|
|
26
|
+
this.server = new Server({
|
|
27
|
+
name: 'hoyalab-server',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
}, {
|
|
30
|
+
capabilities: {
|
|
31
|
+
tools: {},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
const BASE_URL_LOCAL = BASE_URLS[ENV] || BASE_URLS.prod;
|
|
35
|
+
const REGISTRATION_URL_LOCAL = REGISTRATION_URLS[ENV] || REGISTRATION_URLS.prod;
|
|
36
|
+
this.client = new HoyaLabClient(API_KEY, BASE_URL_LOCAL, MASTER_KEY, REGISTRATION_URL_LOCAL);
|
|
37
|
+
this.setupToolHandlers();
|
|
38
|
+
// Error handling
|
|
39
|
+
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
|
40
|
+
process.on('SIGINT', async () => {
|
|
41
|
+
await this.server.close();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
setupToolHandlers() {
|
|
46
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
47
|
+
tools: [
|
|
48
|
+
{
|
|
49
|
+
name: 'get_order',
|
|
50
|
+
description: 'Obter detalhes de um pedido pelo número',
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
numeroPedido: { type: 'string', description: 'Número do pedido' },
|
|
55
|
+
},
|
|
56
|
+
required: ['numeroPedido'],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'track_order',
|
|
61
|
+
description: 'Rastrear pedidos e buscar dados principais (limite de consulta: 90 dias)',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
dataInicio: { type: 'string', description: 'Data de início (YYYY-MM-DD)' },
|
|
66
|
+
dataFim: { type: 'string', description: 'Data de fim (YYYY-MM-DD)' },
|
|
67
|
+
status: { type: 'string', description: 'Status do pedido' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'list_products',
|
|
73
|
+
description: 'Listar todos os produtos disponíveis',
|
|
74
|
+
inputSchema: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'get_product',
|
|
81
|
+
description: 'Obter detalhes de um produto pelo código',
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
codigo: { type: 'string', description: 'Código do produto' },
|
|
86
|
+
},
|
|
87
|
+
required: ['codigo'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'get_payment_conditions',
|
|
92
|
+
description: 'Listar condições de pagamento disponíveis',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
// Adicionar propriedades conforme necessário
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
// Adicionando mais ferramentas conforme a demanda
|
|
101
|
+
{
|
|
102
|
+
name: 'get_materials',
|
|
103
|
+
description: 'Listar materiais disponíveis para as lentes',
|
|
104
|
+
inputSchema: { type: 'object', properties: {} },
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'get_treatments',
|
|
108
|
+
description: 'Listar tratamentos disponíveis (Ex: Antirreflexo)',
|
|
109
|
+
inputSchema: { type: 'object', properties: {} },
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: 'get_colors',
|
|
113
|
+
description: 'Listar colorações/cores disponíveis para as lentes',
|
|
114
|
+
inputSchema: {
|
|
115
|
+
type: 'object',
|
|
116
|
+
properties: {
|
|
117
|
+
codigoProduto: { type: 'string', description: 'Código do produto para filtrar cores compatíveis' },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'get_order_xml',
|
|
123
|
+
description: 'Obter o XML da nota fiscal do pedido',
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: 'object',
|
|
126
|
+
properties: {
|
|
127
|
+
numeroPedido: { type: 'string', description: 'Número do pedido' },
|
|
128
|
+
},
|
|
129
|
+
required: ['numeroPedido'],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'list_frame_types',
|
|
134
|
+
description: 'Listar códigos de tipos de armação (Aro fechado, Nylon, Parafuso)',
|
|
135
|
+
inputSchema: { type: 'object', properties: {} },
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: 'list_designs',
|
|
139
|
+
description: 'Listar desenhos disponíveis (Visão simples, Progressivo, etc)',
|
|
140
|
+
inputSchema: { type: 'object', properties: {} },
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'list_heights',
|
|
144
|
+
description: 'Listar alturas de montagem disponíveis',
|
|
145
|
+
inputSchema: { type: 'object', properties: {} },
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'list_photosensitive_types',
|
|
149
|
+
description: 'Listar tipos de lentes fotossensíveis (Ex: Transitions)',
|
|
150
|
+
inputSchema: { type: 'object', properties: {} },
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'get_visureal_measure',
|
|
154
|
+
description: 'Obter medidas do sistema VisuReal por ID ou referência',
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: 'object',
|
|
157
|
+
properties: {
|
|
158
|
+
sessionId: { type: 'string', description: 'ID da sessão VisuReal' },
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'create_order',
|
|
164
|
+
description: 'Digitar/Criar um novo pedido completo na HoyaLab com validações rigorosas da documentação',
|
|
165
|
+
inputSchema: {
|
|
166
|
+
type: 'object',
|
|
167
|
+
properties: {
|
|
168
|
+
os: { type: 'string', description: 'Número do pedido interno/OS (máx 20 caracteres)' },
|
|
169
|
+
codigoCliente: { type: 'string', description: 'Código do cliente' },
|
|
170
|
+
olho: {
|
|
171
|
+
type: 'integer',
|
|
172
|
+
description: 'Lado do pedido: 1=Ambos, 2=Esquerdo, 3=Direito',
|
|
173
|
+
enum: [1, 2, 3]
|
|
174
|
+
},
|
|
175
|
+
especificacoes: {
|
|
176
|
+
type: 'object',
|
|
177
|
+
properties: {
|
|
178
|
+
codigoProduto: { type: 'string', description: 'Recomendado. Se omitido, deve-se informar desenho, material, etc.' },
|
|
179
|
+
tipoServico: {
|
|
180
|
+
type: 'integer',
|
|
181
|
+
description: '1=Expressa, 3=Convencional(VTA), 4=Sem montagem, 5=Somente corte(Tracer)',
|
|
182
|
+
enum: [1, 3, 4, 5]
|
|
183
|
+
},
|
|
184
|
+
codigoColoracao: { type: 'string' },
|
|
185
|
+
codigoDesenho: { type: 'integer' },
|
|
186
|
+
codigoMaterial: { type: 'integer' },
|
|
187
|
+
codigoTratamento: { type: 'integer' },
|
|
188
|
+
codigoFotossensivel: { type: 'integer' },
|
|
189
|
+
codigoAltura: { type: 'integer' },
|
|
190
|
+
},
|
|
191
|
+
required: ['tipoServico'],
|
|
192
|
+
},
|
|
193
|
+
prescricao: {
|
|
194
|
+
type: 'object',
|
|
195
|
+
properties: {
|
|
196
|
+
esquerdo: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
properties: {
|
|
199
|
+
esferico: { type: 'number' },
|
|
200
|
+
cilindrico: { type: 'number' },
|
|
201
|
+
eixo: { type: 'number' },
|
|
202
|
+
adicao: { type: 'number' },
|
|
203
|
+
prismaH: { type: 'number', description: 'Valor do Prisma Horizontal' },
|
|
204
|
+
basePrismaH: { type: 'string', enum: ['NAS', 'TEM'], description: 'Nasal ou Temporal' },
|
|
205
|
+
prismaV: { type: 'number', description: 'Valor do Prisma Vertical' },
|
|
206
|
+
basePrismaV: { type: 'string', enum: ['SUP', 'INF'], description: 'Superior ou Inferior' },
|
|
207
|
+
dnpLonge: { type: 'number', description: 'DNP (20.5 a 40, múltiplo de 0.5)' },
|
|
208
|
+
alturaPupilar: { type: 'number', description: 'Altura (10 a 34)' },
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
direito: {
|
|
212
|
+
type: 'object',
|
|
213
|
+
properties: {
|
|
214
|
+
esferico: { type: 'number' },
|
|
215
|
+
cilindrico: { type: 'number' },
|
|
216
|
+
eixo: { type: 'number' },
|
|
217
|
+
adicao: { type: 'number' },
|
|
218
|
+
prismaH: { type: 'number' },
|
|
219
|
+
basePrismaH: { type: 'string', enum: ['NAS', 'TEM'] },
|
|
220
|
+
prismaV: { type: 'number' },
|
|
221
|
+
basePrismaV: { type: 'string', enum: ['SUP', 'INF'] },
|
|
222
|
+
dnpLonge: { type: 'number' },
|
|
223
|
+
alturaPupilar: { type: 'number' },
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
afinamentoPrismatico: { type: 'boolean' },
|
|
228
|
+
equilibrioLente: { type: 'boolean' }
|
|
229
|
+
},
|
|
230
|
+
dadosMedida: {
|
|
231
|
+
type: 'object',
|
|
232
|
+
properties: {
|
|
233
|
+
larguraLente: { type: 'number' },
|
|
234
|
+
alturaLente: { type: 'number' },
|
|
235
|
+
ponteLente: { type: 'number' },
|
|
236
|
+
distanciaLeitura: {
|
|
237
|
+
type: 'integer',
|
|
238
|
+
description: 'Em cm: 25, 30, 35, 40, 45, 50, 55',
|
|
239
|
+
enum: [25, 30, 35, 40, 45, 50, 55]
|
|
240
|
+
},
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
armacao: {
|
|
244
|
+
type: 'object',
|
|
245
|
+
properties: {
|
|
246
|
+
tipoArmacao: {
|
|
247
|
+
type: 'integer',
|
|
248
|
+
description: '1=Plástica, 2=Metal, 5=Balgrif, 6=Nylon',
|
|
249
|
+
enum: [1, 2, 5, 6]
|
|
250
|
+
},
|
|
251
|
+
marca: { type: 'string' },
|
|
252
|
+
modelo: { type: 'string' },
|
|
253
|
+
cor: { type: 'string' },
|
|
254
|
+
formaArmacao: {
|
|
255
|
+
type: 'string',
|
|
256
|
+
description: 'Código de 1 a 9 conforme documento'
|
|
257
|
+
},
|
|
258
|
+
comPolimento: { type: 'boolean', description: 'Obrigatório para pedidos com montagem' },
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
camposComplementares: {
|
|
262
|
+
type: 'array',
|
|
263
|
+
items: {
|
|
264
|
+
type: 'object',
|
|
265
|
+
properties: {
|
|
266
|
+
codigo: { type: 'integer' },
|
|
267
|
+
valor: { type: 'string' }
|
|
268
|
+
},
|
|
269
|
+
required: ['codigo', 'valor']
|
|
270
|
+
},
|
|
271
|
+
description: 'Campos técnicos complementares que variam por produto'
|
|
272
|
+
},
|
|
273
|
+
garantia: {
|
|
274
|
+
type: 'object',
|
|
275
|
+
properties: {
|
|
276
|
+
usuarioFinal: { type: 'string', description: 'Nome do usuário final (obrigatório para surfaçadas/prontas)' },
|
|
277
|
+
inicialUsuario: { type: 'string', description: 'Iniciais do usuário' },
|
|
278
|
+
nomeMedico: { type: 'string' },
|
|
279
|
+
crmMedico: { type: 'string' },
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
usaMedidasVisureal: { type: 'boolean', description: 'Usar medidas do sistema Master' }
|
|
283
|
+
},
|
|
284
|
+
required: ['os', 'especificacoes', 'prescricao'],
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
name: 'authenticate',
|
|
289
|
+
description: 'Autentica o usuário usando telefone e código do cliente para liberar o acesso',
|
|
290
|
+
inputSchema: {
|
|
291
|
+
type: 'object',
|
|
292
|
+
properties: {
|
|
293
|
+
telefone: { type: 'string', description: 'Número do telefone com DDD (apenas números)' },
|
|
294
|
+
codigoCliente: { type: 'string', description: 'Código do cliente (ex: 987654)' },
|
|
295
|
+
},
|
|
296
|
+
required: ['telefone', 'codigoCliente'],
|
|
297
|
+
},
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
}));
|
|
301
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
302
|
+
try {
|
|
303
|
+
switch (request.params.name) {
|
|
304
|
+
case 'get_order': {
|
|
305
|
+
const { numeroPedido } = request.params.arguments;
|
|
306
|
+
const data = await this.client.getOrder(numeroPedido);
|
|
307
|
+
return {
|
|
308
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
case 'track_order': {
|
|
312
|
+
const data = await this.client.trackOrder(request.params.arguments);
|
|
313
|
+
return {
|
|
314
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
case 'list_products': {
|
|
318
|
+
const data = await this.client.listProducts();
|
|
319
|
+
return {
|
|
320
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
case 'get_product': {
|
|
324
|
+
const { codigo } = request.params.arguments;
|
|
325
|
+
const data = await this.client.getProductByCode(codigo);
|
|
326
|
+
return {
|
|
327
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
case 'get_payment_conditions': {
|
|
331
|
+
const data = await this.client.getPaymentConditions(request.params.arguments);
|
|
332
|
+
return {
|
|
333
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
case 'get_materials': {
|
|
337
|
+
const data = await this.client.getMaterials();
|
|
338
|
+
return {
|
|
339
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
case 'get_treatments': {
|
|
343
|
+
const data = await this.client.getTreatments();
|
|
344
|
+
return {
|
|
345
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
case 'get_colors': {
|
|
349
|
+
const data = await this.client.getProductColors(request.params.arguments);
|
|
350
|
+
return {
|
|
351
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
case 'get_order_xml': {
|
|
355
|
+
const { numeroPedido } = request.params.arguments;
|
|
356
|
+
const data = await this.client.getOrderXml(numeroPedido);
|
|
357
|
+
return {
|
|
358
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
case 'list_frame_types': {
|
|
362
|
+
const data = await this.client.getFrameTypes();
|
|
363
|
+
return {
|
|
364
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
case 'list_designs': {
|
|
368
|
+
const data = await this.client.getDesigns();
|
|
369
|
+
return {
|
|
370
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
case 'list_heights': {
|
|
374
|
+
const data = await this.client.getHeights();
|
|
375
|
+
return {
|
|
376
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
case 'list_photosensitive_types': {
|
|
380
|
+
const data = await this.client.getPhotosensitiveTypes();
|
|
381
|
+
return {
|
|
382
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
case 'get_visureal_measure': {
|
|
386
|
+
const data = await this.client.getVisuRealMeasure(request.params.arguments);
|
|
387
|
+
return {
|
|
388
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
case 'create_order': {
|
|
392
|
+
const data = await this.client.createOrder(request.params.arguments);
|
|
393
|
+
return {
|
|
394
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
case 'authenticate': {
|
|
398
|
+
const { telefone, codigoCliente } = request.params.arguments;
|
|
399
|
+
const data = await this.client.authenticate(telefone, codigoCliente);
|
|
400
|
+
return {
|
|
401
|
+
content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
default:
|
|
405
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
let errorMessage = error.message || 'Erro desconhecido na execução da ferramenta.';
|
|
410
|
+
// Se for erro do AXIOS (HTTP)
|
|
411
|
+
if (error.response) {
|
|
412
|
+
const apiMessage = error.response.data?.mensagem ||
|
|
413
|
+
(error.response.data?.erros && error.response.data.erros[0]?.mensagem);
|
|
414
|
+
if (apiMessage) {
|
|
415
|
+
errorMessage = `ERRO DA API: ${apiMessage} (Status: ${error.response.status})`;
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
errorMessage = `ERRO DA API (Status: ${error.response.status}): ${JSON.stringify(error.response.data)}`;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
console.error(`[Tool Error ${request.params.name}]`, errorMessage);
|
|
422
|
+
return {
|
|
423
|
+
content: [
|
|
424
|
+
{
|
|
425
|
+
type: 'text',
|
|
426
|
+
text: errorMessage,
|
|
427
|
+
},
|
|
428
|
+
],
|
|
429
|
+
isError: true,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
async run() {
|
|
435
|
+
const transport = new StdioServerTransport();
|
|
436
|
+
await this.server.connect(transport);
|
|
437
|
+
console.error('HoyaLab MCP server running on stdio');
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const server = new HoyaLabServer();
|
|
441
|
+
server.run().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hoyalabmcp",
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"hoyalabmcp": "build/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"build"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"prepublishOnly": "npm run build",
|
|
16
|
+
"start": "node build/index.js",
|
|
17
|
+
"dev": "ts-node src/index.ts"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
24
|
+
"axios": "^1.13.6"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^25.5.0",
|
|
28
|
+
"ts-node": "^10.9.2",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
}
|
|
31
|
+
}
|