certifica-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/.env.example +3 -0
- package/README.md +66 -0
- package/dist/api-client.d.ts +32 -0
- package/dist/api-client.js +167 -0
- package/dist/api-client.js.map +1 -0
- package/dist/app.d.ts +3 -0
- package/dist/app.js +11 -0
- package/dist/app.js.map +1 -0
- package/dist/env.d.ts +9 -0
- package/dist/env.js +10 -0
- package/dist/env.js.map +1 -0
- package/dist/schemas.d.ts +53 -0
- package/dist/schemas.js +48 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +16 -0
- package/dist/server.js.map +1 -0
- package/dist/tools.d.ts +3 -0
- package/dist/tools.js +143 -0
- package/dist/tools.js.map +1 -0
- package/docs/ARCHITECTURE.md +222 -0
- package/docs/CLIENTS.md +98 -0
- package/package.json +48 -0
package/.env.example
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Certifica MCP
|
|
2
|
+
|
|
3
|
+
Servidor MCP para integrar Codex, Claude e outros clientes compatíveis ao fluxo
|
|
4
|
+
editorial do blog Certifica.
|
|
5
|
+
|
|
6
|
+
O projeto será uma camada fina sobre `certifica-backend`. Ele não acessará
|
|
7
|
+
PostgreSQL, Prisma, S3 ou Higgsfield diretamente. Suas ferramentas transformarão
|
|
8
|
+
chamadas MCP em requisições autenticadas à API.
|
|
9
|
+
|
|
10
|
+
## Objetivos
|
|
11
|
+
|
|
12
|
+
- consultar posts e mídias;
|
|
13
|
+
- enviar imagens geradas ou locais;
|
|
14
|
+
- validar artigos;
|
|
15
|
+
- criar e editar rascunhos;
|
|
16
|
+
- agendar ou publicar somente com autorização explícita;
|
|
17
|
+
- funcionar localmente e exclusivamente por `stdio`;
|
|
18
|
+
- ser utilizado pela skill editorial `seo-blog-arquitetura`.
|
|
19
|
+
|
|
20
|
+
## Princípios
|
|
21
|
+
|
|
22
|
+
- `DRAFT` é o estado padrão.
|
|
23
|
+
- Publicação é uma ferramenta e uma permissão separadas.
|
|
24
|
+
- A API é a autoridade de autenticação, validação e persistência.
|
|
25
|
+
- Credenciais nunca entram no código, na skill ou nos argumentos visíveis.
|
|
26
|
+
- Ferramentas retornam dados estruturados e erros acionáveis.
|
|
27
|
+
- Operações mutáveis usam idempotência e deixam trilha de auditoria.
|
|
28
|
+
|
|
29
|
+
## Documentação
|
|
30
|
+
|
|
31
|
+
- [Arquitetura](docs/ARCHITECTURE.md)
|
|
32
|
+
- [Configuração no Codex e Claude](docs/CLIENTS.md)
|
|
33
|
+
- [Roadmap](ROADMAP.md)
|
|
34
|
+
|
|
35
|
+
## Desenvolvimento
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install
|
|
39
|
+
npm test
|
|
40
|
+
npm run build
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
O servidor compilado inicia por `node dist/server.js`. Ele usa exclusivamente
|
|
44
|
+
`stdio`: não abre porta, não oferece transporte HTTP e nunca recebe credenciais
|
|
45
|
+
AWS. A ferramenta `upload_media` envia multipart para a API Certifica; somente a
|
|
46
|
+
API armazena o arquivo no S3.
|
|
47
|
+
|
|
48
|
+
Crie um cliente de integração na API:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm run api-client:create -- "Claude local" usuario posts:read,posts:write,media:read,media:write
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Guarde o token exibido uma única vez e configure o cliente MCP com:
|
|
55
|
+
|
|
56
|
+
- comando: `node`;
|
|
57
|
+
- argumentos: caminho absoluto de `dist/server.js`;
|
|
58
|
+
- `CERTIFICA_API_URL`;
|
|
59
|
+
- `CERTIFICA_API_TOKEN`;
|
|
60
|
+
- `CERTIFICA_API_KEY`, quando exigida pelo ambiente.
|
|
61
|
+
|
|
62
|
+
O fluxo padrão cria `DRAFT`. As ferramentas de agendamento e publicação exigem
|
|
63
|
+
o scope `posts:publish` e o argumento literal `confirm: true`.
|
|
64
|
+
|
|
65
|
+
O servidor carrega `.env` no diretório de trabalho. Em clientes que suportam
|
|
66
|
+
encaminhamento de variáveis, prefira manter os segredos no ambiente do usuário.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AppEnv } from "./env.js";
|
|
2
|
+
import { type PostManifest } from "./schemas.js";
|
|
3
|
+
export declare class ApiError extends Error {
|
|
4
|
+
readonly status: number;
|
|
5
|
+
readonly details?: unknown | undefined;
|
|
6
|
+
constructor(message: string, status: number, details?: unknown | undefined);
|
|
7
|
+
}
|
|
8
|
+
export declare class CertificaApi {
|
|
9
|
+
private readonly env;
|
|
10
|
+
private readonly fetchImpl;
|
|
11
|
+
constructor(env: AppEnv, fetchImpl?: typeof fetch);
|
|
12
|
+
private request;
|
|
13
|
+
listPosts(params: Record<string, string | number | undefined>): Promise<unknown>;
|
|
14
|
+
getPost(id: string): Promise<unknown>;
|
|
15
|
+
searchPosts(q: string, page?: number, pageSize?: number): Promise<unknown>;
|
|
16
|
+
listMedia(page?: number, perPage?: number, q?: string): Promise<unknown>;
|
|
17
|
+
validatePost(post: PostManifest): Promise<unknown>;
|
|
18
|
+
validateExistingPost(id: string, expectedVersion: number): Promise<{
|
|
19
|
+
valid: boolean;
|
|
20
|
+
errors: string[];
|
|
21
|
+
warnings: string[];
|
|
22
|
+
stats: {
|
|
23
|
+
images: number;
|
|
24
|
+
words: number;
|
|
25
|
+
};
|
|
26
|
+
}>;
|
|
27
|
+
createDraft(post: PostManifest, idempotencyKey?: string): Promise<unknown>;
|
|
28
|
+
updatePost(id: string, patch: Record<string, unknown>, idempotencyKey?: string): Promise<unknown>;
|
|
29
|
+
schedulePost(id: string, scheduledFor: string, expectedVersion: number, idempotencyKey?: string): Promise<unknown>;
|
|
30
|
+
publishPost(id: string, expectedVersion: number, idempotencyKey?: string): Promise<unknown>;
|
|
31
|
+
uploadMedia(filePath: string, alt?: string, idempotencyKey?: string): Promise<unknown>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import { extname, resolve } from "node:path";
|
|
4
|
+
import { existingPostSchema, validationResultSchema, } from "./schemas.js";
|
|
5
|
+
export class ApiError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
details;
|
|
8
|
+
constructor(message, status, details) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.details = details;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export class CertificaApi {
|
|
15
|
+
env;
|
|
16
|
+
fetchImpl;
|
|
17
|
+
constructor(env, fetchImpl = fetch) {
|
|
18
|
+
this.env = env;
|
|
19
|
+
this.fetchImpl = fetchImpl;
|
|
20
|
+
}
|
|
21
|
+
async request(path, init = {}, idempotencyKey) {
|
|
22
|
+
const headers = new Headers(init.headers);
|
|
23
|
+
headers.set("authorization", `Bearer ${this.env.CERTIFICA_API_TOKEN}`);
|
|
24
|
+
if (this.env.CERTIFICA_API_KEY) {
|
|
25
|
+
headers.set("x-api-key", this.env.CERTIFICA_API_KEY);
|
|
26
|
+
}
|
|
27
|
+
if (idempotencyKey)
|
|
28
|
+
headers.set("idempotency-key", idempotencyKey);
|
|
29
|
+
if (init.body && !(init.body instanceof FormData)) {
|
|
30
|
+
headers.set("content-type", "application/json");
|
|
31
|
+
}
|
|
32
|
+
const url = new URL(path, `${this.env.CERTIFICA_API_URL.replace(/\/$/, "")}/`);
|
|
33
|
+
const requestInit = {
|
|
34
|
+
...init,
|
|
35
|
+
headers,
|
|
36
|
+
signal: init.signal ?? AbortSignal.timeout(120_000),
|
|
37
|
+
};
|
|
38
|
+
const mayRetry = !init.method || init.method === "GET";
|
|
39
|
+
let response;
|
|
40
|
+
let lastError;
|
|
41
|
+
for (let attempt = 0; attempt < (mayRetry ? 3 : 1); attempt += 1) {
|
|
42
|
+
try {
|
|
43
|
+
response = await this.fetchImpl(url, requestInit);
|
|
44
|
+
if (!mayRetry ||
|
|
45
|
+
(response.status !== 429 && response.status < 500) ||
|
|
46
|
+
attempt === 2) {
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
lastError = error;
|
|
52
|
+
if (!mayRetry || attempt === 2)
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, 150 * 2 ** attempt));
|
|
56
|
+
}
|
|
57
|
+
if (!response)
|
|
58
|
+
throw lastError ?? new Error("A API não respondeu.");
|
|
59
|
+
const body = await response.json().catch(() => null);
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
const message = body &&
|
|
62
|
+
typeof body === "object" &&
|
|
63
|
+
"message" in body &&
|
|
64
|
+
typeof body.message === "string"
|
|
65
|
+
? body.message
|
|
66
|
+
: `A API respondeu com HTTP ${response.status}.`;
|
|
67
|
+
throw new ApiError(message, response.status, body);
|
|
68
|
+
}
|
|
69
|
+
return body;
|
|
70
|
+
}
|
|
71
|
+
listPosts(params) {
|
|
72
|
+
const search = new URLSearchParams();
|
|
73
|
+
for (const [key, value] of Object.entries(params)) {
|
|
74
|
+
if (value !== undefined)
|
|
75
|
+
search.set(key, String(value));
|
|
76
|
+
}
|
|
77
|
+
return this.request(`blog/admin/posts?${search}`);
|
|
78
|
+
}
|
|
79
|
+
getPost(id) {
|
|
80
|
+
return this.request(`blog/admin/posts/${id}`);
|
|
81
|
+
}
|
|
82
|
+
searchPosts(q, page = 1, pageSize = 10) {
|
|
83
|
+
const search = new URLSearchParams({
|
|
84
|
+
q,
|
|
85
|
+
page: String(page),
|
|
86
|
+
pageSize: String(pageSize),
|
|
87
|
+
});
|
|
88
|
+
return this.request(`blog/posts/search?${search}`);
|
|
89
|
+
}
|
|
90
|
+
listMedia(page = 1, perPage = 20, q) {
|
|
91
|
+
const search = new URLSearchParams({
|
|
92
|
+
page: String(page),
|
|
93
|
+
perPage: String(perPage),
|
|
94
|
+
});
|
|
95
|
+
if (q)
|
|
96
|
+
search.set("q", q);
|
|
97
|
+
return this.request(`blog/media?${search}`);
|
|
98
|
+
}
|
|
99
|
+
validatePost(post) {
|
|
100
|
+
return this.request("blog/admin/posts/validate", {
|
|
101
|
+
method: "POST",
|
|
102
|
+
body: JSON.stringify(post),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
async validateExistingPost(id, expectedVersion) {
|
|
106
|
+
const post = existingPostSchema.parse(await this.getPost(id));
|
|
107
|
+
if (post.version !== expectedVersion) {
|
|
108
|
+
throw new ApiError(`Conflito de versão. Atual: ${post.version}.`, 409);
|
|
109
|
+
}
|
|
110
|
+
const validation = validationResultSchema.parse(await this.validatePost({
|
|
111
|
+
...post,
|
|
112
|
+
excerpt: post.excerpt ?? undefined,
|
|
113
|
+
seoTitle: post.seoTitle ?? undefined,
|
|
114
|
+
metaDescription: post.metaDescription ?? undefined,
|
|
115
|
+
focusKeyword: post.focusKeyword ?? undefined,
|
|
116
|
+
visibility: "PUBLIC",
|
|
117
|
+
categoryNames: [],
|
|
118
|
+
tagNames: [],
|
|
119
|
+
}));
|
|
120
|
+
if (!validation.valid) {
|
|
121
|
+
throw new ApiError(`Post inválido: ${validation.errors.join(" ")}`, 422, validation);
|
|
122
|
+
}
|
|
123
|
+
return validation;
|
|
124
|
+
}
|
|
125
|
+
createDraft(post, idempotencyKey = randomUUID()) {
|
|
126
|
+
return this.request("blog/posts", {
|
|
127
|
+
method: "POST",
|
|
128
|
+
body: JSON.stringify({ ...post, status: "DRAFT" }),
|
|
129
|
+
}, idempotencyKey);
|
|
130
|
+
}
|
|
131
|
+
updatePost(id, patch, idempotencyKey = randomUUID()) {
|
|
132
|
+
return this.request(`blog/admin/posts/${id}`, { method: "PATCH", body: JSON.stringify(patch) }, idempotencyKey);
|
|
133
|
+
}
|
|
134
|
+
schedulePost(id, scheduledFor, expectedVersion, idempotencyKey) {
|
|
135
|
+
return this.updatePost(id, { status: "SCHEDULED", scheduledFor, expectedVersion }, idempotencyKey);
|
|
136
|
+
}
|
|
137
|
+
publishPost(id, expectedVersion, idempotencyKey) {
|
|
138
|
+
return this.updatePost(id, { status: "PUBLISHED", expectedVersion }, idempotencyKey);
|
|
139
|
+
}
|
|
140
|
+
async uploadMedia(filePath, alt, idempotencyKey = randomUUID()) {
|
|
141
|
+
const absolutePath = resolve(filePath);
|
|
142
|
+
const fileInfo = await stat(absolutePath);
|
|
143
|
+
if (!fileInfo.isFile())
|
|
144
|
+
throw new Error("O caminho não aponta para um arquivo.");
|
|
145
|
+
if (fileInfo.size === 0)
|
|
146
|
+
throw new Error("O arquivo está vazio.");
|
|
147
|
+
if (fileInfo.size > 25 * 1024 * 1024) {
|
|
148
|
+
throw new Error("A imagem deve ter no máximo 25 MB.");
|
|
149
|
+
}
|
|
150
|
+
const mimeByExtension = {
|
|
151
|
+
".jpg": "image/jpeg",
|
|
152
|
+
".jpeg": "image/jpeg",
|
|
153
|
+
".png": "image/png",
|
|
154
|
+
".webp": "image/webp",
|
|
155
|
+
};
|
|
156
|
+
const extension = extname(absolutePath).toLowerCase();
|
|
157
|
+
const mimeType = mimeByExtension[extension];
|
|
158
|
+
if (!mimeType)
|
|
159
|
+
throw new Error("Use uma imagem JPEG, PNG ou WebP.");
|
|
160
|
+
const form = new FormData();
|
|
161
|
+
if (alt?.trim())
|
|
162
|
+
form.set("alt", alt.trim());
|
|
163
|
+
form.set("file", new Blob([await readFile(absolutePath)], { type: mimeType }), `upload${extension}`);
|
|
164
|
+
return this.request("blog/media/upload", { method: "POST", body: form }, idempotencyKey);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,EACL,kBAAkB,EAClB,sBAAsB,GAEvB,MAAM,cAAc,CAAC;AAEtB,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGtB;IACA;IAHX,YACE,OAAe,EACN,MAAc,EACd,OAAiB;QAE1B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHN,WAAM,GAAN,MAAM,CAAQ;QACd,YAAO,GAAP,OAAO,CAAU;IAG5B,CAAC;CACF;AAED,MAAM,OAAO,YAAY;IAEJ;IACA;IAFnB,YACmB,GAAW,EACX,YAA0B,KAAK;QAD/B,QAAG,GAAH,GAAG,CAAQ;QACX,cAAS,GAAT,SAAS,CAAsB;IAC/C,CAAC;IAEI,KAAK,CAAC,OAAO,CACnB,IAAY,EACZ,OAAoB,EAAE,EACtB,cAAuB;QAEvB,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,IAAI,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC,CAAC;QACvE,IAAI,IAAI,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC;YAC/B,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QACvD,CAAC;QACD,IAAI,cAAc;YAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;QACnE,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,YAAY,QAAQ,CAAC,EAAE,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAClD,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CACjB,IAAI,EACJ,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,CACpD,CAAC;QACF,MAAM,WAAW,GAAG;YAClB,GAAG,IAAI;YACP,OAAO;YACP,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC;SACpD,CAAC;QACF,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC;QACvD,IAAI,QAA8B,CAAC;QACnC,IAAI,SAAkB,CAAC;QAEvB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;YACjE,IAAI,CAAC;gBACH,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;gBAClD,IACE,CAAC,QAAQ;oBACT,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;oBAClD,OAAO,KAAK,CAAC,EACb,CAAC;oBACD,MAAM;gBACR,CAAC;YACH,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,SAAS,GAAG,KAAK,CAAC;gBAClB,IAAI,CAAC,QAAQ,IAAI,OAAO,KAAK,CAAC;oBAAE,MAAM,KAAK,CAAC;YAC9C,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE,CACjC,UAAU,CAAC,YAAY,EAAE,GAAG,GAAG,CAAC,IAAI,OAAO,CAAC,CAC7C,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,QAAQ;YAAE,MAAM,SAAS,IAAI,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACpE,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,OAAO,GACX,IAAI;gBACJ,OAAO,IAAI,KAAK,QAAQ;gBACxB,SAAS,IAAI,IAAI;gBACjB,OAAO,IAAI,CAAC,OAAO,KAAK,QAAQ;gBAC9B,CAAC,CAAC,IAAI,CAAC,OAAO;gBACd,CAAC,CAAC,4BAA4B,QAAQ,CAAC,MAAM,GAAG,CAAC;YACrD,MAAM,IAAI,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACrD,CAAC;QACD,OAAO,IAAS,CAAC;IACnB,CAAC;IAED,SAAS,CAAC,MAAmD;QAC3D,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QACrC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAClD,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1D,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAU,oBAAoB,MAAM,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,OAAO,CAAC,EAAU;QAChB,OAAO,IAAI,CAAC,OAAO,CAAU,oBAAoB,EAAE,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,WAAW,CAAC,CAAS,EAAE,IAAI,GAAG,CAAC,EAAE,QAAQ,GAAG,EAAE;QAC5C,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YACjC,CAAC;YACD,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;YAClB,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;SAC3B,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,OAAO,CAAU,qBAAqB,MAAM,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,SAAS,CAAC,IAAI,GAAG,CAAC,EAAE,OAAO,GAAG,EAAE,EAAE,CAAU;QAC1C,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC;YACjC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC;YAClB,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC;SACzB,CAAC,CAAC;QACH,IAAI,CAAC;YAAE,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC,OAAO,CAAU,cAAc,MAAM,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,YAAY,CAAC,IAAkB;QAC7B,OAAO,IAAI,CAAC,OAAO,CAAU,2BAA2B,EAAE;YACxD,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,EAAU,EAAE,eAAuB;QAC5D,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QAC9D,IAAI,IAAI,CAAC,OAAO,KAAK,eAAe,EAAE,CAAC;YACrC,MAAM,IAAI,QAAQ,CAAC,8BAA8B,IAAI,CAAC,OAAO,GAAG,EAAE,GAAG,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,UAAU,GAAG,sBAAsB,CAAC,KAAK,CAC7C,MAAM,IAAI,CAAC,YAAY,CAAC;YACtB,GAAG,IAAI;YACP,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,SAAS;YAClC,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,SAAS;YACpC,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,SAAS;YAClD,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,SAAS;YAC5C,UAAU,EAAE,QAAQ;YACpB,aAAa,EAAE,EAAE;YACjB,QAAQ,EAAE,EAAE;SACb,CAAC,CACH,CAAC;QACF,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;YACtB,MAAM,IAAI,QAAQ,CAChB,kBAAkB,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,EAC/C,GAAG,EACH,UAAU,CACX,CAAC;QACJ,CAAC;QACD,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,WAAW,CAAC,IAAkB,EAAE,iBAAyB,UAAU,EAAE;QACnE,OAAO,IAAI,CAAC,OAAO,CACjB,YAAY,EACZ;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;SACnD,EACD,cAAc,CACf,CAAC;IACJ,CAAC;IAED,UAAU,CACR,EAAU,EACV,KAA8B,EAC9B,iBAAyB,UAAU,EAAE;QAErC,OAAO,IAAI,CAAC,OAAO,CACjB,oBAAoB,EAAE,EAAE,EACxB,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAChD,cAAc,CACf,CAAC;IACJ,CAAC;IAED,YAAY,CACV,EAAU,EACV,YAAoB,EACpB,eAAuB,EACvB,cAAuB;QAEvB,OAAO,IAAI,CAAC,UAAU,CACpB,EAAE,EACF,EAAE,MAAM,EAAE,WAAW,EAAE,YAAY,EAAE,eAAe,EAAE,EACtD,cAAc,CACf,CAAC;IACJ,CAAC;IAED,WAAW,CAAC,EAAU,EAAE,eAAuB,EAAE,cAAuB;QACtE,OAAO,IAAI,CAAC,UAAU,CACpB,EAAE,EACF,EAAE,MAAM,EAAE,WAAW,EAAE,eAAe,EAAE,EACxC,cAAc,CACf,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CACf,QAAgB,EAChB,GAAY,EACZ,iBAAyB,UAAU,EAAE;QAErC,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1C,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE;YACpB,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC3D,IAAI,QAAQ,CAAC,IAAI,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;QAClE,IAAI,QAAQ,CAAC,IAAI,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,eAAe,GAA2B;YAC9C,MAAM,EAAE,YAAY;YACpB,OAAO,EAAE,YAAY;YACrB,MAAM,EAAE,WAAW;YACnB,OAAO,EAAE,YAAY;SACtB,CAAC;QACF,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,WAAW,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAEpE,MAAM,IAAI,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,GAAG,EAAE,IAAI,EAAE;YAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,GAAG,CACN,MAAM,EACN,IAAI,IAAI,CAAC,CAAC,MAAM,QAAQ,CAAC,YAAY,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,EAC5D,SAAS,SAAS,EAAE,CACrB,CAAC;QAEF,OAAO,IAAI,CAAC,OAAO,CACjB,mBAAmB,EACnB,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,EAC9B,cAAc,CACf,CAAC;IACJ,CAAC;CACF"}
|
package/dist/app.d.ts
ADDED
package/dist/app.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { registerTools } from "./tools.js";
|
|
3
|
+
export function createServer(api) {
|
|
4
|
+
const server = new McpServer({
|
|
5
|
+
name: "certifica-blog",
|
|
6
|
+
version: "0.1.0",
|
|
7
|
+
});
|
|
8
|
+
registerTools(server, api);
|
|
9
|
+
return server;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGpE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,UAAU,YAAY,CAAC,GAAiB;IAC5C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IACH,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3B,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/env.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
declare const envSchema: z.ZodObject<{
|
|
3
|
+
CERTIFICA_API_URL: z.ZodString;
|
|
4
|
+
CERTIFICA_API_TOKEN: z.ZodString;
|
|
5
|
+
CERTIFICA_API_KEY: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export type AppEnv = z.infer<typeof envSchema>;
|
|
8
|
+
export declare function loadEnv(source?: NodeJS.ProcessEnv): AppEnv;
|
|
9
|
+
export {};
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const envSchema = z.object({
|
|
3
|
+
CERTIFICA_API_URL: z.string().url(),
|
|
4
|
+
CERTIFICA_API_TOKEN: z.string().min(20),
|
|
5
|
+
CERTIFICA_API_KEY: z.string().min(1).optional(),
|
|
6
|
+
});
|
|
7
|
+
export function loadEnv(source = process.env) {
|
|
8
|
+
return envSchema.parse(source);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=env.js.map
|
package/dist/env.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IACzB,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IACnC,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC;IACvC,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAChD,CAAC,CAAC;AAIH,MAAM,UAAU,OAAO,CAAC,SAA4B,OAAO,CAAC,GAAG;IAC7D,OAAO,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const editorContentSchema: z.ZodObject<{
|
|
3
|
+
format: z.ZodLiteral<"html">;
|
|
4
|
+
version: z.ZodLiteral<1>;
|
|
5
|
+
html: z.ZodString;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export declare const postManifestSchema: z.ZodObject<{
|
|
8
|
+
title: z.ZodString;
|
|
9
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
10
|
+
excerpt: z.ZodOptional<z.ZodString>;
|
|
11
|
+
seoTitle: z.ZodOptional<z.ZodString>;
|
|
12
|
+
metaDescription: z.ZodOptional<z.ZodString>;
|
|
13
|
+
focusKeyword: z.ZodOptional<z.ZodString>;
|
|
14
|
+
imageCredit: z.ZodOptional<z.ZodString>;
|
|
15
|
+
content: z.ZodObject<{
|
|
16
|
+
format: z.ZodLiteral<"html">;
|
|
17
|
+
version: z.ZodLiteral<1>;
|
|
18
|
+
html: z.ZodString;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
coverId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
21
|
+
visibility: z.ZodDefault<z.ZodEnum<{
|
|
22
|
+
PUBLIC: "PUBLIC";
|
|
23
|
+
UNLISTED: "UNLISTED";
|
|
24
|
+
PRIVATE: "PRIVATE";
|
|
25
|
+
}>>;
|
|
26
|
+
categoryNames: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
27
|
+
tagNames: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
export type PostManifest = z.infer<typeof postManifestSchema>;
|
|
30
|
+
export declare const existingPostSchema: z.ZodObject<{
|
|
31
|
+
content: z.ZodObject<{
|
|
32
|
+
format: z.ZodLiteral<"html">;
|
|
33
|
+
version: z.ZodLiteral<1>;
|
|
34
|
+
html: z.ZodString;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
title: z.ZodString;
|
|
37
|
+
coverId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
38
|
+
id: z.ZodString;
|
|
39
|
+
version: z.ZodNumber;
|
|
40
|
+
excerpt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
41
|
+
seoTitle: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
42
|
+
metaDescription: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
43
|
+
focusKeyword: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export declare const validationResultSchema: z.ZodObject<{
|
|
46
|
+
valid: z.ZodBoolean;
|
|
47
|
+
errors: z.ZodArray<z.ZodString>;
|
|
48
|
+
warnings: z.ZodArray<z.ZodString>;
|
|
49
|
+
stats: z.ZodObject<{
|
|
50
|
+
images: z.ZodNumber;
|
|
51
|
+
words: z.ZodNumber;
|
|
52
|
+
}, z.core.$strip>;
|
|
53
|
+
}, z.core.$strip>;
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const editorContentSchema = z.object({
|
|
3
|
+
format: z.literal("html"),
|
|
4
|
+
version: z.literal(1),
|
|
5
|
+
html: z.string().min(1).max(250_000),
|
|
6
|
+
});
|
|
7
|
+
export const postManifestSchema = z.object({
|
|
8
|
+
title: z.string().min(3).max(160),
|
|
9
|
+
slug: z.string().min(1).max(140).optional(),
|
|
10
|
+
excerpt: z.string().max(300).optional(),
|
|
11
|
+
seoTitle: z.string().max(60).optional(),
|
|
12
|
+
metaDescription: z.string().max(160).optional(),
|
|
13
|
+
focusKeyword: z.string().max(160).optional(),
|
|
14
|
+
imageCredit: z.string().max(300).optional(),
|
|
15
|
+
content: editorContentSchema,
|
|
16
|
+
coverId: z.string().uuid().nullable().optional(),
|
|
17
|
+
visibility: z.enum(["PUBLIC", "UNLISTED", "PRIVATE"]).default("PUBLIC"),
|
|
18
|
+
categoryNames: z.array(z.string().min(1).max(60)).max(20).default([]),
|
|
19
|
+
tagNames: z.array(z.string().min(1).max(60)).max(30).default([]),
|
|
20
|
+
});
|
|
21
|
+
export const existingPostSchema = postManifestSchema
|
|
22
|
+
.pick({
|
|
23
|
+
title: true,
|
|
24
|
+
excerpt: true,
|
|
25
|
+
seoTitle: true,
|
|
26
|
+
metaDescription: true,
|
|
27
|
+
focusKeyword: true,
|
|
28
|
+
content: true,
|
|
29
|
+
coverId: true,
|
|
30
|
+
})
|
|
31
|
+
.extend({
|
|
32
|
+
id: z.string().uuid(),
|
|
33
|
+
version: z.number().int().positive(),
|
|
34
|
+
excerpt: z.string().max(300).nullable().optional(),
|
|
35
|
+
seoTitle: z.string().max(60).nullable().optional(),
|
|
36
|
+
metaDescription: z.string().max(160).nullable().optional(),
|
|
37
|
+
focusKeyword: z.string().max(160).nullable().optional(),
|
|
38
|
+
});
|
|
39
|
+
export const validationResultSchema = z.object({
|
|
40
|
+
valid: z.boolean(),
|
|
41
|
+
errors: z.array(z.string()),
|
|
42
|
+
warnings: z.array(z.string()),
|
|
43
|
+
stats: z.object({
|
|
44
|
+
images: z.number().int(),
|
|
45
|
+
words: z.number().int(),
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACzB,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC;CACrC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAC3C,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IACvC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE;IACvC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAC/C,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAC5C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IAC3C,OAAO,EAAE,mBAAmB;IAC5B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAChD,UAAU,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IACvE,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACrE,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CACjE,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,kBAAkB,GAAG,kBAAkB;KACjD,IAAI,CAAC;IACJ,KAAK,EAAE,IAAI;IACX,OAAO,EAAE,IAAI;IACb,QAAQ,EAAE,IAAI;IACd,eAAe,EAAE,IAAI;IACrB,YAAY,EAAE,IAAI;IAClB,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,IAAI;CACd,CAAC;KACD,MAAM,CAAC;IACN,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE;IACrB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACpC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAClD,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAClD,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IAC1D,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACxD,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE;IAClB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;QACd,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;QACxB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;KACxB,CAAC;CACH,CAAC,CAAC"}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { createServer } from "./app.js";
|
|
5
|
+
import { CertificaApi } from "./api-client.js";
|
|
6
|
+
import { loadEnv } from "./env.js";
|
|
7
|
+
async function main() {
|
|
8
|
+
const server = createServer(new CertificaApi(loadEnv()));
|
|
9
|
+
await server.connect(new StdioServerTransport());
|
|
10
|
+
console.error("Certifica MCP iniciado por stdio.");
|
|
11
|
+
}
|
|
12
|
+
main().catch((error) => {
|
|
13
|
+
console.error("Falha ao iniciar Certifica MCP:", error instanceof Error ? error.message : error);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
16
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AAEA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACzD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;AACrD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CACX,iCAAiC,EACjC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAC/C,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
package/dist/tools.d.ts
ADDED
package/dist/tools.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ApiError } from "./api-client.js";
|
|
3
|
+
import { postManifestSchema } from "./schemas.js";
|
|
4
|
+
function result(data) {
|
|
5
|
+
return {
|
|
6
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function errorResult(error) {
|
|
10
|
+
const message = error instanceof ApiError
|
|
11
|
+
? `${error.message} (HTTP ${error.status})`
|
|
12
|
+
: error instanceof Error
|
|
13
|
+
? error.message
|
|
14
|
+
: "Erro inesperado.";
|
|
15
|
+
return {
|
|
16
|
+
isError: true,
|
|
17
|
+
content: [{ type: "text", text: message }],
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function safely(handler) {
|
|
21
|
+
return async (...args) => {
|
|
22
|
+
try {
|
|
23
|
+
return result(await handler(...args));
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
return errorResult(error);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function registerTools(server, api) {
|
|
31
|
+
server.registerTool("list_posts", {
|
|
32
|
+
description: "Lista posts administrativos para descoberta e linkagem interna.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
page: z.number().int().positive().default(1),
|
|
35
|
+
pageSize: z.number().int().min(1).max(100).default(20),
|
|
36
|
+
category: z.string().optional(),
|
|
37
|
+
tag: z.string().optional(),
|
|
38
|
+
},
|
|
39
|
+
annotations: { readOnlyHint: true },
|
|
40
|
+
}, safely(({ page, pageSize, category, tag }) => api.listPosts({ page, pageSize, category, tag })));
|
|
41
|
+
server.registerTool("get_post", {
|
|
42
|
+
description: "Obtém um post administrativo completo pelo UUID.",
|
|
43
|
+
inputSchema: { id: z.string().uuid() },
|
|
44
|
+
annotations: { readOnlyHint: true },
|
|
45
|
+
}, safely(({ id }) => api.getPost(id)));
|
|
46
|
+
server.registerTool("search_posts", {
|
|
47
|
+
description: "Pesquisa posts públicos para linkagem interna.",
|
|
48
|
+
inputSchema: {
|
|
49
|
+
query: z.string().min(1),
|
|
50
|
+
page: z.number().int().positive().default(1),
|
|
51
|
+
pageSize: z.number().int().min(1).max(100).default(10),
|
|
52
|
+
},
|
|
53
|
+
annotations: { readOnlyHint: true },
|
|
54
|
+
}, safely(({ query, page, pageSize }) => api.searchPosts(query, page, pageSize)));
|
|
55
|
+
server.registerTool("list_media", {
|
|
56
|
+
description: "Lista mídias existentes no CMS.",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
page: z.number().int().positive().default(1),
|
|
59
|
+
perPage: z.number().int().min(1).max(100).default(20),
|
|
60
|
+
query: z.string().optional(),
|
|
61
|
+
},
|
|
62
|
+
annotations: { readOnlyHint: true },
|
|
63
|
+
}, safely(({ page, perPage, query }) => api.listMedia(page, perPage, query)));
|
|
64
|
+
server.registerTool("upload_media", {
|
|
65
|
+
description: "Envia uma imagem local JPEG, PNG ou WebP de até 25 MB para a biblioteca Certifica.",
|
|
66
|
+
inputSchema: {
|
|
67
|
+
filePath: z
|
|
68
|
+
.string()
|
|
69
|
+
.min(1)
|
|
70
|
+
.describe("Caminho local absoluto da imagem"),
|
|
71
|
+
alt: z.string().max(200).optional(),
|
|
72
|
+
idempotencyKey: z.string().min(8).max(200).optional(),
|
|
73
|
+
},
|
|
74
|
+
annotations: { readOnlyHint: false, idempotentHint: false },
|
|
75
|
+
}, safely(({ filePath, alt, idempotencyKey }) => api.uploadMedia(filePath, alt, idempotencyKey)));
|
|
76
|
+
server.registerTool("validate_post", {
|
|
77
|
+
description: "Valida conteúdo, SEO, capa, imagens e placeholders antes de salvar.",
|
|
78
|
+
inputSchema: postManifestSchema.shape,
|
|
79
|
+
annotations: { readOnlyHint: true },
|
|
80
|
+
}, safely((post) => api.validatePost(post)));
|
|
81
|
+
server.registerTool("create_post_draft", {
|
|
82
|
+
description: "Cria um rascunho. Esta ferramenta nunca publica nem agenda o post.",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
...postManifestSchema.shape,
|
|
85
|
+
idempotencyKey: z.string().min(8).max(200).optional(),
|
|
86
|
+
},
|
|
87
|
+
annotations: {
|
|
88
|
+
readOnlyHint: false,
|
|
89
|
+
destructiveHint: false,
|
|
90
|
+
idempotentHint: true,
|
|
91
|
+
},
|
|
92
|
+
}, safely(({ idempotencyKey, ...post }) => api.createDraft(post, idempotencyKey)));
|
|
93
|
+
server.registerTool("update_post", {
|
|
94
|
+
description: "Atualiza um post existente com controle otimista de versão.",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
id: z.string().uuid(),
|
|
97
|
+
expectedVersion: z.number().int().positive(),
|
|
98
|
+
patch: postManifestSchema.partial(),
|
|
99
|
+
idempotencyKey: z.string().min(8).max(200).optional(),
|
|
100
|
+
},
|
|
101
|
+
annotations: {
|
|
102
|
+
readOnlyHint: false,
|
|
103
|
+
destructiveHint: true,
|
|
104
|
+
idempotentHint: true,
|
|
105
|
+
},
|
|
106
|
+
}, safely(({ id, expectedVersion, patch, idempotencyKey }) => api.updatePost(id, { ...patch, expectedVersion }, idempotencyKey)));
|
|
107
|
+
server.registerTool("schedule_post", {
|
|
108
|
+
description: "Agenda um post. Use somente após confirmação explícita do usuário.",
|
|
109
|
+
inputSchema: {
|
|
110
|
+
id: z.string().uuid(),
|
|
111
|
+
expectedVersion: z.number().int().positive(),
|
|
112
|
+
scheduledFor: z.string().datetime(),
|
|
113
|
+
confirm: z.literal(true),
|
|
114
|
+
idempotencyKey: z.string().min(8).max(200).optional(),
|
|
115
|
+
},
|
|
116
|
+
annotations: {
|
|
117
|
+
readOnlyHint: false,
|
|
118
|
+
destructiveHint: true,
|
|
119
|
+
idempotentHint: true,
|
|
120
|
+
},
|
|
121
|
+
}, safely(async ({ id, expectedVersion, scheduledFor, idempotencyKey }) => {
|
|
122
|
+
await api.validateExistingPost(id, expectedVersion);
|
|
123
|
+
return api.schedulePost(id, scheduledFor, expectedVersion, idempotencyKey);
|
|
124
|
+
}));
|
|
125
|
+
server.registerTool("publish_post", {
|
|
126
|
+
description: "Publica um post imediatamente. Exige confirmação explícita do usuário.",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
id: z.string().uuid(),
|
|
129
|
+
expectedVersion: z.number().int().positive(),
|
|
130
|
+
confirm: z.literal(true),
|
|
131
|
+
idempotencyKey: z.string().min(8).max(200).optional(),
|
|
132
|
+
},
|
|
133
|
+
annotations: {
|
|
134
|
+
readOnlyHint: false,
|
|
135
|
+
destructiveHint: true,
|
|
136
|
+
idempotentHint: true,
|
|
137
|
+
},
|
|
138
|
+
}, safely(async ({ id, expectedVersion, idempotencyKey }) => {
|
|
139
|
+
await api.validateExistingPost(id, expectedVersion);
|
|
140
|
+
return api.publishPost(id, expectedVersion, idempotencyKey);
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# Arquitetura do Certifica MCP
|
|
2
|
+
|
|
3
|
+
## Contexto
|
|
4
|
+
|
|
5
|
+
O sistema editorial terá duas camadas complementares:
|
|
6
|
+
|
|
7
|
+
- `seo-blog-arquitetura`: regras de redação, SEO, legibilidade, branding,
|
|
8
|
+
imagens, ABNT e orquestração;
|
|
9
|
+
- `certifica-mcp`: ferramentas determinísticas para consultar e alterar dados por
|
|
10
|
+
meio da API.
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
Codex / Claude
|
|
14
|
+
|
|
|
15
|
+
| MCP (stdio local)
|
|
16
|
+
v
|
|
17
|
+
certifica-mcp
|
|
18
|
+
|
|
|
19
|
+
| HTTPS + token de integração + Idempotency-Key
|
|
20
|
+
v
|
|
21
|
+
certifica-backend
|
|
22
|
+
|
|
|
23
|
+
+-- PostgreSQL
|
|
24
|
+
+-- S3
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
O gerador de imagens não faz parte do MCP:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
Skill -> Higgsfield no Claude ou Imagegen no Codex
|
|
31
|
+
-> arquivo da imagem
|
|
32
|
+
-> certifica.upload_media
|
|
33
|
+
-> API
|
|
34
|
+
-> S3
|
|
35
|
+
-> URL/ID
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Isso mantém o fluxo independente do fornecedor de geração.
|
|
39
|
+
|
|
40
|
+
## Limites de responsabilidade
|
|
41
|
+
|
|
42
|
+
### Skill editorial
|
|
43
|
+
|
|
44
|
+
- transformar pesquisa em artigo;
|
|
45
|
+
- aplicar thresholds de legibilidade;
|
|
46
|
+
- produzir metadados e HTML TinyMCE;
|
|
47
|
+
- consultar links internos pelas ferramentas;
|
|
48
|
+
- criar briefs e prompts de imagem;
|
|
49
|
+
- gerar alt, legenda e fonte;
|
|
50
|
+
- substituir placeholders pelas URLs retornadas;
|
|
51
|
+
- pedir revisão antes de publicar.
|
|
52
|
+
|
|
53
|
+
### MCP
|
|
54
|
+
|
|
55
|
+
- validar argumentos com schemas estritos;
|
|
56
|
+
- chamar endpoints da API;
|
|
57
|
+
- propagar `Idempotency-Key`;
|
|
58
|
+
- normalizar respostas e erros;
|
|
59
|
+
- limitar tamanho e tipo de arquivos antes do envio;
|
|
60
|
+
- ocultar tokens e detalhes internos;
|
|
61
|
+
- expor capabilities claras ao modelo.
|
|
62
|
+
|
|
63
|
+
### API
|
|
64
|
+
|
|
65
|
+
- autenticar a identidade da integração;
|
|
66
|
+
- autorizar scopes;
|
|
67
|
+
- validar novamente todos os dados;
|
|
68
|
+
- sanitizar HTML;
|
|
69
|
+
- armazenar posts, relações e mídias;
|
|
70
|
+
- hospedar imagens no S3;
|
|
71
|
+
- calcular métricas de conteúdo;
|
|
72
|
+
- auditar alterações.
|
|
73
|
+
|
|
74
|
+
## Transporte: somente stdio
|
|
75
|
+
|
|
76
|
+
O cliente inicia um processo local. Logs devem ir para `stderr`; `stdout` fica
|
|
77
|
+
reservado exclusivamente ao protocolo MCP.
|
|
78
|
+
|
|
79
|
+
Vantagens:
|
|
80
|
+
|
|
81
|
+
- configuração simples;
|
|
82
|
+
- nenhuma porta pública;
|
|
83
|
+
- superfície de ataque menor;
|
|
84
|
+
- compatível com distribuição por npm.
|
|
85
|
+
|
|
86
|
+
O projeto não terá Streamable HTTP, SSE, servidor web, OAuth de transporte ou
|
|
87
|
+
porta de escuta. Cada usuário executa sua própria instância local, iniciada pelo
|
|
88
|
+
Claude ou Codex. O MCP local chama `certifica-backend` por HTTPS.
|
|
89
|
+
|
|
90
|
+
## Ferramentas planejadas
|
|
91
|
+
|
|
92
|
+
| Ferramenta | Scope da API | Mutação | Confirmação |
|
|
93
|
+
| ------------------- | --------------- | ------- | --------------------------- |
|
|
94
|
+
| `list_posts` | `posts:read` | não | não |
|
|
95
|
+
| `get_post` | `posts:read` | não | não |
|
|
96
|
+
| `search_posts` | `posts:read` | não | não |
|
|
97
|
+
| `list_media` | `media:read` | não | não |
|
|
98
|
+
| `upload_media` | `media:write` | sim | implícita no pedido |
|
|
99
|
+
| `validate_post` | `posts:write` | não | não |
|
|
100
|
+
| `create_post_draft` | `posts:write` | sim | implícita no pedido |
|
|
101
|
+
| `update_post` | `posts:write` | sim | mostrar resumo da alteração |
|
|
102
|
+
| `schedule_post` | `posts:publish` | sim | explícita |
|
|
103
|
+
| `publish_post` | `posts:publish` | sim | explícita |
|
|
104
|
+
|
|
105
|
+
Não será criada uma ferramenta genérica como `request_api`. Ferramentas genéricas
|
|
106
|
+
ampliam permissões, dificultam validação e tornam o comportamento do modelo menos
|
|
107
|
+
previsível.
|
|
108
|
+
|
|
109
|
+
## Contrato editorial
|
|
110
|
+
|
|
111
|
+
O MCP trabalhará com um manifesto intermediário:
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{
|
|
115
|
+
"title": "Título",
|
|
116
|
+
"slug": "titulo",
|
|
117
|
+
"excerpt": "Resumo",
|
|
118
|
+
"seoTitle": "Título SEO",
|
|
119
|
+
"metaDescription": "Descrição",
|
|
120
|
+
"focusKeyword": "palavra-chave",
|
|
121
|
+
"content": {
|
|
122
|
+
"format": "html",
|
|
123
|
+
"version": 1,
|
|
124
|
+
"html": "<p>...</p>"
|
|
125
|
+
},
|
|
126
|
+
"coverId": "uuid",
|
|
127
|
+
"categoryNames": ["Acessibilidade"],
|
|
128
|
+
"tagNames": ["NBR 9050"],
|
|
129
|
+
"visibility": "PUBLIC",
|
|
130
|
+
"status": "DRAFT"
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Imagens do corpo já devem estar hospedadas antes de criar o draft. O HTML não pode
|
|
135
|
+
conter `URL-FIGURA-*`, `blob:`, `data:` ou URLs locais.
|
|
136
|
+
|
|
137
|
+
## Autenticação
|
|
138
|
+
|
|
139
|
+
O `API_KEY` atual identifica a aplicação, mas não é suficiente como identidade de
|
|
140
|
+
automação. A API deverá emitir tokens individuais, armazenados somente como hash,
|
|
141
|
+
com:
|
|
142
|
+
|
|
143
|
+
- cliente;
|
|
144
|
+
- autor associado;
|
|
145
|
+
- scopes;
|
|
146
|
+
- estado ativo/revogado;
|
|
147
|
+
- expiração opcional;
|
|
148
|
+
- último uso.
|
|
149
|
+
|
|
150
|
+
Configuração local esperada:
|
|
151
|
+
|
|
152
|
+
```text
|
|
153
|
+
CERTIFICA_API_URL=https://api.exemplo.com
|
|
154
|
+
CERTIFICA_API_TOKEN=<segredo individual>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
O MCP nunca deve imprimir essas variáveis. Cada usuário recebe uma credencial
|
|
158
|
+
individual para a API.
|
|
159
|
+
|
|
160
|
+
## Idempotência
|
|
161
|
+
|
|
162
|
+
Uploads, criação, edição, agendamento e publicação receberão uma chave de
|
|
163
|
+
idempotência. A API persistirá:
|
|
164
|
+
|
|
165
|
+
- cliente;
|
|
166
|
+
- chave;
|
|
167
|
+
- operação;
|
|
168
|
+
- hash do payload;
|
|
169
|
+
- status e resposta;
|
|
170
|
+
- expiração.
|
|
171
|
+
|
|
172
|
+
Repetir a mesma chave e o mesmo payload devolve a resposta anterior. Reutilizar a
|
|
173
|
+
chave com outro payload falha.
|
|
174
|
+
|
|
175
|
+
## Imagens
|
|
176
|
+
|
|
177
|
+
Tipos iniciais: JPEG, PNG e WebP. Limite: 25 MiB.
|
|
178
|
+
|
|
179
|
+
Papéis:
|
|
180
|
+
|
|
181
|
+
- `cover`: preferência por 16:9;
|
|
182
|
+
- `body`: 3:2, panorâmica ou proporção justificada pelo conteúdo.
|
|
183
|
+
|
|
184
|
+
O MCP recebe arquivo local legível ou conteúdo binário suportado pelo transporte,
|
|
185
|
+
chama o endpoint multipart e devolve o recurso `Media`. A API continua responsável
|
|
186
|
+
por assinatura real, metadados, orientação, S3 e compensação.
|
|
187
|
+
|
|
188
|
+
Fonte sugerida para imagem gerada:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
Fonte: Elaborado pelo autor com auxílio do Higgsfield (2026).
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
O nome da ferramenta deve ser fornecido pelo fluxo gerador; o MCP não deve
|
|
195
|
+
inventá-lo.
|
|
196
|
+
|
|
197
|
+
## Segurança de publicação
|
|
198
|
+
|
|
199
|
+
- Criar sempre como `DRAFT`.
|
|
200
|
+
- Não combinar criação e publicação na mesma ferramenta.
|
|
201
|
+
- Exigir `posts:publish` para agendar/publicar.
|
|
202
|
+
- Antes de publicar, obter a versão atual e validar conflitos.
|
|
203
|
+
- A API deve aceitar controle otimista por `updatedAt` ou versão.
|
|
204
|
+
- Respostas de mutação devem incluir ID, slug, status e URL de revisão.
|
|
205
|
+
- Registrar cliente, autor, ação, post e horário na auditoria.
|
|
206
|
+
|
|
207
|
+
## Distribuição
|
|
208
|
+
|
|
209
|
+
Primeiro formato:
|
|
210
|
+
|
|
211
|
+
```text
|
|
212
|
+
npx -y @certifica/blog-mcp
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
O pacote publicado deve conter apenas o build, licença e metadados necessários.
|
|
216
|
+
O token fica na configuração do cliente MCP, nunca no pacote.
|
|
217
|
+
|
|
218
|
+
Depois, podem ser oferecidos:
|
|
219
|
+
|
|
220
|
+
- pacote npm privado/público;
|
|
221
|
+
- executável por plataforma;
|
|
222
|
+
- instalador ou atualização automática.
|
package/docs/CLIENTS.md
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Configurar clientes MCP
|
|
2
|
+
|
|
3
|
+
Compile primeiro:
|
|
4
|
+
|
|
5
|
+
```powershell
|
|
6
|
+
cd A:\Desenvolvimento\Projetos\Certifica\certifica-mcp
|
|
7
|
+
npm install
|
|
8
|
+
npm run build
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
O processo usa somente `stdio`. Defina as credenciais no ambiente do processo ou
|
|
12
|
+
em um `.env` no diretório de trabalho do MCP:
|
|
13
|
+
|
|
14
|
+
```env
|
|
15
|
+
CERTIFICA_API_URL=http://127.0.0.1:3333
|
|
16
|
+
CERTIFICA_API_TOKEN=certifica_TOKEN_INDIVIDUAL
|
|
17
|
+
CERTIFICA_API_KEY=CHAVE_GERAL_DA_API
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Nunca versione o `.env`.
|
|
21
|
+
|
|
22
|
+
## Codex
|
|
23
|
+
|
|
24
|
+
Adicione ao `~/.codex/config.toml` ou ao `.codex/config.toml` de um projeto
|
|
25
|
+
confiável:
|
|
26
|
+
|
|
27
|
+
```toml
|
|
28
|
+
[mcp_servers.certifica]
|
|
29
|
+
enabled = true
|
|
30
|
+
required = true
|
|
31
|
+
command = "node"
|
|
32
|
+
args = ["A:\\Desenvolvimento\\Projetos\\Certifica\\certifica-mcp\\dist\\server.js"]
|
|
33
|
+
cwd = "A:\\Desenvolvimento\\Projetos\\Certifica\\certifica-mcp"
|
|
34
|
+
startup_timeout_sec = 15.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Com `cwd` apontando para a pasta do MCP, o servidor carrega o `.env` local. Como
|
|
38
|
+
alternativa, use `env_vars` para encaminhar variáveis já definidas no sistema:
|
|
39
|
+
|
|
40
|
+
```toml
|
|
41
|
+
env_vars = [
|
|
42
|
+
"CERTIFICA_API_URL",
|
|
43
|
+
"CERTIFICA_API_TOKEN",
|
|
44
|
+
"CERTIFICA_API_KEY",
|
|
45
|
+
]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Reinicie o Codex e confirme que aparecem dez ferramentas do servidor
|
|
49
|
+
`certifica`.
|
|
50
|
+
|
|
51
|
+
## Claude Code
|
|
52
|
+
|
|
53
|
+
No Windows, execute:
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
claude mcp add certifica --scope user -- node A:\Desenvolvimento\Projetos\Certifica\certifica-mcp\dist\server.js
|
|
57
|
+
claude mcp get certifica
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Defina as três variáveis no ambiente do usuário antes de iniciar o Claude. Para
|
|
61
|
+
uma configuração de projeto explícita, use `.mcp.json`:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcpServers": {
|
|
66
|
+
"certifica": {
|
|
67
|
+
"type": "stdio",
|
|
68
|
+
"command": "node",
|
|
69
|
+
"args": [
|
|
70
|
+
"A:\\Desenvolvimento\\Projetos\\Certifica\\certifica-mcp\\dist\\server.js"
|
|
71
|
+
],
|
|
72
|
+
"env": {
|
|
73
|
+
"CERTIFICA_API_URL": "${CERTIFICA_API_URL:-http://127.0.0.1:3333}",
|
|
74
|
+
"CERTIFICA_API_TOKEN": "${CERTIFICA_API_TOKEN}",
|
|
75
|
+
"CERTIFICA_API_KEY": "${CERTIFICA_API_KEY}"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Defina os segredos no sistema antes de iniciar o Claude. Não grave tokens reais
|
|
83
|
+
em `.mcp.json` compartilhado.
|
|
84
|
+
|
|
85
|
+
## Verificação
|
|
86
|
+
|
|
87
|
+
1. Chame `list_media`.
|
|
88
|
+
2. Chame `validate_post` com um manifesto mínimo.
|
|
89
|
+
3. Envie uma imagem local com `upload_media` e chave idempotente estável.
|
|
90
|
+
4. Crie um draft somente depois de a validação retornar `valid: true`.
|
|
91
|
+
5. Não teste `publish_post` sem um token com `posts:publish` e uma confirmação
|
|
92
|
+
explícita.
|
|
93
|
+
|
|
94
|
+
Com a API local e as variáveis configuradas, execute a verificação não mutável:
|
|
95
|
+
|
|
96
|
+
```powershell
|
|
97
|
+
npm run smoke
|
|
98
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "certifica-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Servidor MCP para integrar Codex, Claude e outros clientes compatíveis ao fluxo editorial do blog Certifica.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/server.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"certifica-mcp": "dist/server.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"docs",
|
|
13
|
+
".env.example",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"dev": "tsx src/server.ts",
|
|
18
|
+
"build": "tsc -p tsconfig.json",
|
|
19
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"smoke": "tsx scripts/smoke.ts",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"format": "prettier --write .",
|
|
24
|
+
"format:check": "prettier --check ."
|
|
25
|
+
},
|
|
26
|
+
"keywords": [],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
34
|
+
"dotenv": "^17.4.2",
|
|
35
|
+
"ky": "^2.0.2",
|
|
36
|
+
"zod": "^4.4.3"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@eslint/js": "^10.0.1",
|
|
40
|
+
"@types/node": "^26.1.2",
|
|
41
|
+
"eslint": "^10.8.0",
|
|
42
|
+
"prettier": "^3.9.6",
|
|
43
|
+
"tsx": "^4.23.1",
|
|
44
|
+
"typescript": "^5.9.3",
|
|
45
|
+
"typescript-eslint": "^8.65.0",
|
|
46
|
+
"vitest": "^4.1.10"
|
|
47
|
+
}
|
|
48
|
+
}
|