sankhya-mcp 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marcio Nogueira
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # sankhya-mcp
2
+
3
+ > **Projeto em fase inicial — novas versões e funcionalidades em breve.**
4
+
5
+ Servidor MCP (Model Context Protocol) para documentação do **Sankhya ERP**, com busca semântica local via RAG. Permite que o Claude (VS Code, Claude Desktop, etc.) responda perguntas precisas sobre o sistema Sankhya sem necessidade de chaves de API em runtime.
6
+
7
+ ---
8
+
9
+ ## Como funciona
10
+
11
+ O conhecimento fica embutido no pacote como um índice vetorial local (`sqlite-vec`). O servidor roda localmente via STDIO e responde às queries do Claude com os trechos de documentação mais relevantes.
12
+
13
+ ```
14
+ Usuário pergunta ao Claude
15
+
16
+ Claude chama search_docs (MCP tool)
17
+
18
+ Servidor busca no índice local (sem API externa)
19
+
20
+ Claude responde com base na documentação do Sankhya
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Instalação — VS Code
26
+
27
+ Adicione ao `.mcp.json` do seu projeto:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "sankhya-docs": {
33
+ "command": "npx",
34
+ "args": ["sankhya-mcp"]
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ ## Instalação — Claude Desktop
41
+
42
+ Adicione ao `claude_desktop_config.json`:
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "sankhya-docs": {
48
+ "command": "npx",
49
+ "args": ["sankhya-mcp"]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ ---
56
+
57
+ ## Coleções disponíveis
58
+
59
+ | Coleção | Descrição |
60
+ |---|---|
61
+ | `boletos-emissao` | Emissão de boletos e exportação de XMLs de NF-e |
62
+ | `dashboards-html5` | Criação e otimização de dashboards HTML5 |
63
+ | `reabertura-ops` | Correção e reabertura de ordens de produção |
64
+
65
+ ---
66
+
67
+ ## Exemplos de perguntas
68
+
69
+ - *Como exportar XMLs de NF-e no Sankhya?*
70
+ - *Como criar um dashboard HTML5?*
71
+ - *Como otimizar queries para dashboards?*
72
+ - *Como reabrir uma ordem de produção?*
73
+ - *Como corrigir apontamentos de OP com quantidade errada?*
74
+
75
+ ---
76
+
77
+ ## Stack
78
+
79
+ - Python 3.12+
80
+ - [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk) + FastMCP
81
+ - [sqlite-vec](https://github.com/asg017/sqlite-vec) — índice vetorial embutido
82
+ - [fastembed](https://github.com/qdrant/fastembed) — embeddings locais (sem API externa)
83
+ - Modelo: `paraphrase-multilingual-MiniLM-L12-v2` (384 dims, multilingual)
84
+
85
+ ---
86
+
87
+ ## Status
88
+
89
+ Este projeto está em **fase inicial de desenvolvimento**. Em breve:
90
+
91
+ - Novas coleções de documentação
92
+ - Melhorias na qualidade da busca
93
+ - Publicação no npm para uso via `npx`
94
+
95
+ Sugestões e contribuições são bem-vindas via [Issues](https://github.com/mfnogueira/sankhya-mcp/issues).
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ const { spawn } = require("child_process");
3
+ const path = require("path");
4
+
5
+ const packageDir = path.join(__dirname, "..");
6
+ const serverScript = path.join(packageDir, "src", "server.py");
7
+
8
+ const proc = spawn("uv", ["run", "python", serverScript], {
9
+ cwd: packageDir,
10
+ stdio: "inherit",
11
+ });
12
+
13
+ proc.on("error", (err) => {
14
+ if (err.code === "ENOENT") {
15
+ process.stderr.write(
16
+ "Error: 'uv' is required but not installed.\n" +
17
+ "Install it with: curl -LsSf https://astral.sh/uv/install.sh | sh\n"
18
+ );
19
+ } else {
20
+ process.stderr.write(`Error: ${err.message}\n`);
21
+ }
22
+ process.exit(1);
23
+ });
24
+
25
+ proc.on("exit", (code) => process.exit(code ?? 0));
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "sankhya-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Sankhya ERP documentation — semantic search via sqlite-vec",
5
+ "license": "MIT",
6
+ "keywords": ["mcp", "sankhya", "erp", "documentation", "rag"],
7
+ "homepage": "https://github.com/mfnogueira/sankhya-mcp",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/mfnogueira/sankhya-mcp.git"
11
+ },
12
+ "bin": {
13
+ "sankhya-mcp": "./bin/sankhya-mcp.js"
14
+ },
15
+ "files": [
16
+ "bin/",
17
+ "src/",
18
+ "pyproject.toml",
19
+ "README.md"
20
+ ]
21
+ }
package/pyproject.toml ADDED
@@ -0,0 +1,35 @@
1
+ [project]
2
+ name = "sankhya-mcp"
3
+ version = "0.1.0"
4
+ description = "MCP server for Sankhya ERP documentation — semantic search via sqlite-vec"
5
+ readme = "README.md"
6
+ requires-python = ">=3.12"
7
+ dependencies = [
8
+ "mcp[cli]>=1.2.0",
9
+ "sqlite-vec>=0.1.0",
10
+ "fastembed>=0.4.0",
11
+ ]
12
+
13
+ [project.optional-dependencies]
14
+ ingest = [
15
+ "openai>=1.0.0",
16
+ "python-dotenv>=1.0.0",
17
+ "tiktoken>=0.7.0",
18
+ "numpy>=1.26.0",
19
+ ]
20
+
21
+ [project.scripts]
22
+ sankhya-mcp = "src.server:main"
23
+
24
+ [build-system]
25
+ requires = ["hatchling"]
26
+ build-backend = "hatchling.build"
27
+
28
+ [tool.hatch.build.targets.wheel]
29
+ packages = ["src"]
30
+
31
+ [tool.hatch.build.targets.sdist.force-include]
32
+ "src/data/index.db" = "src/data/index.db"
33
+
34
+ [tool.hatch.build.targets.wheel.force-include]
35
+ "src/data/index.db" = "src/data/index.db"
File without changes
Binary file
package/src/server.py ADDED
@@ -0,0 +1,183 @@
1
+ """
2
+ Servidor MCP para documentação do Sankhya — transporte STDIO.
3
+
4
+ IMPORTANTE: nunca usar print() — corrompe o protocolo JSON-RPC.
5
+ Todo output de log vai para sys.stderr.
6
+ """
7
+
8
+ import logging
9
+ import sqlite3
10
+ import struct
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ import sqlite_vec
15
+ from fastembed import TextEmbedding
16
+ from mcp.server.fastmcp import FastMCP
17
+
18
+ # ── Logging ──────────────────────────────────────────────────────────────────
19
+ logging.basicConfig(
20
+ stream=sys.stderr,
21
+ level=logging.INFO,
22
+ format="%(asctime)s %(name)s %(message)s",
23
+ datefmt="%H:%M:%S",
24
+ )
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # ── Constantes ───────────────────────────────────────────────────────────────
28
+ DB_PATH = Path(__file__).parent / "data" / "index.db"
29
+ MODEL_NAME = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
30
+ EMBEDDING_DIM = 384
31
+
32
+ # ── Modelo de embedding (carregado sob demanda) ───────────────────────────────
33
+ _model: TextEmbedding | None = None
34
+
35
+
36
+ def _get_model() -> TextEmbedding:
37
+ global _model
38
+ if _model is None:
39
+ logger.info(f"Carregando modelo de embedding: {MODEL_NAME}")
40
+ _model = TextEmbedding(model_name=MODEL_NAME)
41
+ logger.info("Modelo carregado.")
42
+ return _model
43
+
44
+
45
+ def _embed_query(text: str) -> bytes:
46
+ """Gera o embedding da query e serializa para bytes (formato sqlite-vec)."""
47
+ model = _get_model()
48
+ vectors = list(model.embed([text]))
49
+ vec = vectors[0].tolist()
50
+ return struct.pack(f"{len(vec)}f", *vec)
51
+
52
+
53
+ def _get_db() -> sqlite3.Connection:
54
+ con = sqlite3.connect(DB_PATH)
55
+ con.enable_load_extension(True)
56
+ sqlite_vec.load(con)
57
+ con.enable_load_extension(False)
58
+ return con
59
+
60
+
61
+ # ── FastMCP ───────────────────────────────────────────────────────────────────
62
+ mcp = FastMCP("sankhya-docs")
63
+
64
+
65
+ @mcp.tool()
66
+ def search_docs(query: str, collection: str | None = None, top_k: int = 5) -> str:
67
+ """Busca documentação do Sankhya ERP com base em uma query semântica.
68
+
69
+ Use esta ferramenta quando o usuário perguntar sobre funcionalidades,
70
+ configurações, erros, fluxos de trabalho ou qualquer aspecto do sistema
71
+ Sankhya. Retorna os trechos de documentação mais relevantes.
72
+
73
+ Args:
74
+ query: Pergunta ou termo a buscar (em português ou inglês).
75
+ collection: Nome da coleção para filtrar resultados (opcional).
76
+ Use list_collections() para ver as coleções disponíveis.
77
+ top_k: Número de resultados a retornar (padrão: 5).
78
+ """
79
+ if not DB_PATH.exists():
80
+ return "Índice de documentação não encontrado. Execute o pipeline de ingestão primeiro."
81
+
82
+ logger.info(f"search_docs: query='{query}' collection={collection!r} top_k={top_k}")
83
+
84
+ try:
85
+ query_vec = _embed_query(query)
86
+ except Exception as exc:
87
+ logger.error(f"Erro ao gerar embedding: {exc}")
88
+ return f"Erro ao processar a query: {exc}"
89
+
90
+ try:
91
+ con = _get_db()
92
+
93
+ # Busca os k mais próximos (ampliado para filtrar por coleção depois)
94
+ candidates = top_k * 10 if collection else top_k
95
+
96
+ rows = con.execute(
97
+ """
98
+ SELECT c.text, c.source_file, c.collection, c.type, e.distance
99
+ FROM (
100
+ SELECT rowid, distance
101
+ FROM embeddings
102
+ WHERE embedding MATCH ?
103
+ AND k = ?
104
+ ORDER BY distance
105
+ ) e
106
+ JOIN chunks c ON c.id = e.rowid
107
+ """,
108
+ (query_vec, candidates),
109
+ ).fetchall()
110
+
111
+ con.close()
112
+ except Exception as exc:
113
+ logger.error(f"Erro na consulta ao índice: {exc}")
114
+ return f"Erro ao consultar o índice: {exc}"
115
+
116
+ # Filtra por coleção se solicitado
117
+ if collection:
118
+ rows = [r for r in rows if r[2] == collection]
119
+
120
+ rows = rows[:top_k]
121
+
122
+ if not rows:
123
+ msg = f"Nenhum resultado encontrado para '{query}'"
124
+ if collection:
125
+ msg += f" na coleção '{collection}'"
126
+ return msg
127
+
128
+ # Formata a resposta
129
+ parts: list[str] = [f"## Resultados para: {query}\n"]
130
+
131
+ for i, (text, source_file, coll, doc_type, distance) in enumerate(rows, 1):
132
+ similarity = round((1 - distance) * 100, 1)
133
+ type_label = "📷 Imagem" if doc_type == "image_description" else "📄 Documento"
134
+ parts.append(
135
+ f"### [{i}] {source_file} — {coll} ({type_label}, {similarity}% relevância)\n\n{text}"
136
+ )
137
+
138
+ return "\n\n---\n\n".join(parts)
139
+
140
+
141
+ @mcp.tool()
142
+ def list_collections() -> str:
143
+ """Lista todas as coleções de documentação do Sankhya disponíveis no índice.
144
+
145
+ Use esta ferramenta antes de search_docs quando quiser restringir a busca
146
+ a uma área específica do sistema, ou para dar ao usuário uma visão geral
147
+ do que está documentado.
148
+ """
149
+ if not DB_PATH.exists():
150
+ return "Índice de documentação não encontrado. Execute o pipeline de ingestão primeiro."
151
+
152
+ try:
153
+ con = sqlite3.connect(DB_PATH)
154
+ rows = con.execute(
155
+ "SELECT collection, COUNT(*) as total FROM chunks GROUP BY collection ORDER BY collection"
156
+ ).fetchall()
157
+ con.close()
158
+ except Exception as exc:
159
+ logger.error(f"Erro ao consultar coleções: {exc}")
160
+ return f"Erro ao consultar o índice: {exc}"
161
+
162
+ if not rows:
163
+ return "Índice vazio — nenhuma coleção encontrada."
164
+
165
+ total_chunks = sum(r[1] for r in rows)
166
+ lines = ["## Coleções de documentação disponíveis\n"]
167
+
168
+ for collection, count in rows:
169
+ lines.append(f"- **{collection}** — {count} trechos indexados")
170
+
171
+ lines.append(f"\n_Total: {total_chunks} trechos em {len(rows)} coleções._")
172
+
173
+ return "\n".join(lines)
174
+
175
+
176
+ # ── Entry point ───────────────────────────────────────────────────────────────
177
+ def main() -> None:
178
+ logger.info(f"Iniciando servidor sankhya-docs (DB: {DB_PATH})")
179
+ mcp.run(transport="stdio")
180
+
181
+
182
+ if __name__ == "__main__":
183
+ main()