@mesaque/apikibot-mcp 1.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.
Files changed (4) hide show
  1. package/README.md +43 -0
  2. package/client.js +105 -0
  3. package/index.js +71 -0
  4. package/package.json +17 -0
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # ApikiBot MCP Server
2
+
3
+ MCP server para integração do Claude Code com o ApikiBot. Permite executar comandos de infraestrutura diretamente pelo Claude Code com controle de acesso por role (RBAC).
4
+
5
+ ## Instalação
6
+
7
+ ```bash
8
+ npx -y @mesaque/apikibot-mcp
9
+ ```
10
+
11
+ ## Configuração
12
+
13
+ Solicite ao administrador sua API key e adicione ao `~/.claude/settings.json`:
14
+
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "apikibot": {
19
+ "command": "npx",
20
+ "args": ["-y", "@mesaque/apikibot-mcp"],
21
+ "env": {
22
+ "BOT_API_URL": "URL_FORNECIDA_PELO_ADMIN",
23
+ "BOT_API_KEY": "SUA_CHAVE_AQUI"
24
+ }
25
+ }
26
+ }
27
+ }
28
+ ```
29
+
30
+ ## Variáveis de ambiente
31
+
32
+ | Variável | Descrição |
33
+ |---|---|
34
+ | `BOT_API_URL` | URL da API do bot (fornecida pelo admin) |
35
+ | `BOT_API_KEY` | Sua API key pessoal (fornecida pelo admin) |
36
+
37
+ ## Tools disponíveis
38
+
39
+ As tools visíveis dependem da role associada à sua API key. O admin gerencia as permissões.
40
+
41
+ ## Para o administrador
42
+
43
+ Consulte a documentação interna em `.claude/knowledge/apikibot.md` para instruções de configuração de keys e roles.
package/client.js ADDED
@@ -0,0 +1,105 @@
1
+ import https from 'node:https';
2
+ import http from 'node:http';
3
+
4
+ export class BotClient {
5
+ constructor(apiUrl, apiKey) {
6
+ this.apiUrl = apiUrl.replace(/\/$/, '');
7
+ this.apiKey = apiKey;
8
+ this.jwtToken = null;
9
+ this.tokenExpiry = 0;
10
+ }
11
+
12
+ async authenticate() {
13
+ const body = JSON.stringify({
14
+ command: 'auth',
15
+ function: 'generateToken',
16
+ apiKey: this.apiKey,
17
+ });
18
+
19
+ const data = await this._request(body, false);
20
+
21
+ if (!data.token) {
22
+ throw new Error('Falha na autenticação: resposta inválida do bot');
23
+ }
24
+
25
+ this.jwtToken = data.token;
26
+ this.tokenExpiry = Date.now() + (23 * 60 * 60 * 1000); // 23h
27
+ }
28
+
29
+ async listTools() {
30
+ if (Date.now() >= this.tokenExpiry) {
31
+ await this.authenticate();
32
+ }
33
+ return this._request(
34
+ JSON.stringify({ command: 'auth', function: 'listTools' }),
35
+ true
36
+ );
37
+ }
38
+
39
+ async call(command, fn, params = {}) {
40
+ if (Date.now() >= this.tokenExpiry) {
41
+ await this.authenticate();
42
+ }
43
+
44
+ const body = JSON.stringify({ command, function: fn, ...params });
45
+
46
+ try {
47
+ return await this._request(body, true);
48
+ } catch (err) {
49
+ if (err.statusCode === 401) {
50
+ await this.authenticate();
51
+ return this._request(body, true);
52
+ }
53
+ throw err;
54
+ }
55
+ }
56
+
57
+ _request(body, withAuth) {
58
+ return new Promise((resolve, reject) => {
59
+ const url = new URL(this.apiUrl);
60
+ const isHttps = url.protocol === 'https:';
61
+ const lib = isHttps ? https : http;
62
+
63
+ const options = {
64
+ hostname: url.hostname,
65
+ port: url.port || (isHttps ? 443 : 80),
66
+ path: url.pathname || '/',
67
+ method: 'POST',
68
+ headers: {
69
+ 'Content-Type': 'application/json',
70
+ 'Content-Length': Buffer.byteLength(body),
71
+ },
72
+ };
73
+
74
+ if (withAuth && this.jwtToken) {
75
+ options.headers['Authorization'] = `Bearer ${this.jwtToken}`;
76
+ }
77
+
78
+ const req = lib.request(options, (res) => {
79
+ let raw = '';
80
+ res.on('data', chunk => { raw += chunk; });
81
+ res.on('end', () => {
82
+ if (res.statusCode === 403) {
83
+ const err = new Error('Proibido pelo servidor (403)');
84
+ err.statusCode = 403;
85
+ return reject(err);
86
+ }
87
+ if (res.statusCode === 401) {
88
+ const err = new Error('Não autorizado (401)');
89
+ err.statusCode = 401;
90
+ return reject(err);
91
+ }
92
+ try {
93
+ resolve(JSON.parse(raw));
94
+ } catch {
95
+ reject(new Error(`Resposta inválida do bot: ${raw.slice(0, 200)}`));
96
+ }
97
+ });
98
+ });
99
+
100
+ req.on('error', reject);
101
+ req.write(body);
102
+ req.end();
103
+ });
104
+ }
105
+ }
package/index.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { z } from 'zod';
6
+ import { BotClient } from './client.js';
7
+
8
+ const BOT_API_URL = process.env.BOT_API_URL;
9
+ const BOT_API_KEY = process.env.BOT_API_KEY;
10
+
11
+ if (!BOT_API_URL || !BOT_API_KEY) {
12
+ process.stderr.write(
13
+ 'ERRO: As variáveis de ambiente BOT_API_URL e BOT_API_KEY são obrigatórias.\n' +
14
+ 'Configure no settings.json do Claude Code:\n' +
15
+ ' "env": { "BOT_API_URL": "https://example.com", "BOT_API_KEY": "apk_..." }\n'
16
+ );
17
+ process.exit(1);
18
+ }
19
+
20
+ const client = new BotClient(BOT_API_URL, BOT_API_KEY);
21
+
22
+ try {
23
+ await client.authenticate();
24
+ } catch (err) {
25
+ process.stderr.write(`ERRO ao autenticar com o bot: ${err.message}\n`);
26
+ process.exit(1);
27
+ }
28
+
29
+ let tools;
30
+ try {
31
+ tools = await client.listTools();
32
+ } catch (err) {
33
+ process.stderr.write(`ERRO ao buscar lista de tools do bot: ${err.message}\n`);
34
+ process.exit(1);
35
+ }
36
+
37
+ const server = new McpServer({
38
+ name: 'apikibot',
39
+ version: '1.0.0',
40
+ });
41
+
42
+ // Constrói schema zod a partir da definição de params do manifest
43
+ function buildSchema(params) {
44
+ if (!params?.length) return {};
45
+ const shape = {};
46
+ for (const p of params) {
47
+ let field = p.type === 'object'
48
+ ? z.object({}).passthrough()
49
+ : z.string();
50
+ if (p.description) field = field.describe(p.description);
51
+ if (!p.required) field = field.optional();
52
+ shape[p.name] = field;
53
+ }
54
+ return shape;
55
+ }
56
+
57
+ // Registrar todas as tools dinamicamente a partir do manifest remoto
58
+ for (const tool of tools) {
59
+ const schema = buildSchema(tool.params);
60
+ server.tool(tool.name, tool.description, schema, async (params) => {
61
+ try {
62
+ const result = await client.call(tool.command, tool.function, params);
63
+ return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
64
+ } catch (e) {
65
+ return { content: [{ type: 'text', text: `Erro: ${e.message}` }], isError: true };
66
+ }
67
+ });
68
+ }
69
+
70
+ const transport = new StdioServerTransport();
71
+ await server.connect(transport);
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@mesaque/apikibot-mcp",
3
+ "version": "1.1.0",
4
+ "description": "MCP server para integração do Claude Code com o ApikiBot",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "apikibot-mcp": "index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node index.js"
12
+ },
13
+ "dependencies": {
14
+ "@modelcontextprotocol/sdk": "^1.12.0",
15
+ "zod": "^3.23.0"
16
+ }
17
+ }