@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.
- package/README.md +43 -0
- package/client.js +105 -0
- package/index.js +71 -0
- 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
|
+
}
|