devvo-ops-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/README.md ADDED
@@ -0,0 +1,155 @@
1
+ # Devvo Ops MCP
2
+
3
+ Servidor MCP local para integrar clientes como Cursor, Codex, Claude Desktop e outros agentes ao gerenciador de projetos Devvo Ops. A primeira tool disponível é `get_task_by_key`, que consulta uma task por uma key como `TASK-100`.
4
+
5
+ ```text
6
+ AI Client
7
+ ↓ MCP stdio
8
+ Devvo Ops MCP
9
+ ↓ HTTP(S)
10
+ Devvo Ops API
11
+ ```
12
+
13
+ ## Arquitetura
14
+
15
+ O MCP é um cliente externo da API, embora seu código esteja atualmente no mesmo repositório.
16
+
17
+ > Mesmo estando no mesmo repositório, o MCP não deve importar services, repositories, entities, controllers, banco de dados ou outras implementações internas da API.
18
+
19
+ ```text
20
+ CORRETO
21
+
22
+ MCP
23
+ ↓ HTTP
24
+ API
25
+ ↓
26
+ Services
27
+ ```
28
+
29
+ ```text
30
+ INCORRETO
31
+
32
+ MCP
33
+ ↓ import
34
+ TaskService
35
+ ```
36
+
37
+ Essa separação permite publicar o pacote independentemente no NPM, reduz acoplamento, evita dependência da implementação interna, mantém autenticação e autorização na API e permite que ambos evoluam separadamente.
38
+
39
+ ## Estrutura
40
+
41
+ ```text
42
+ src/
43
+ ├── api/ # transporte HTTP, cliente da API e mapeamento de respostas
44
+ ├── auth/ # gerenciamento de tokens e persistência da sessão
45
+ ├── cli/ # prompts interativos do CLI
46
+ ├── tools/ # tools MCP, sem conhecimento de autenticação
47
+ └── index.ts # comandos CLI e servidor MCP stdio
48
+ ```
49
+
50
+ ## Desenvolvimento
51
+
52
+ Requer Node.js 20 ou superior.
53
+
54
+ ```bash
55
+ cd mcp
56
+ npm install
57
+ npm run build
58
+ npm test
59
+ ```
60
+
61
+ A URL da API é configurada por `DEVVO_OPS_API_URL`. O valor padrão para desenvolvimento local é `http://localhost:3334`.
62
+
63
+ ## Autenticação
64
+
65
+ ```bash
66
+ npx devvo-ops-mcp login
67
+ ```
68
+
69
+ ```text
70
+ email + senha
71
+ ↓
72
+ API
73
+ ↓
74
+ refresh token
75
+ ↓
76
+ ~/.devvo-ops-mcp/auth.json
77
+ ```
78
+
79
+ A senha nunca é persistida. O access token permanece somente na memória do processo; apenas o refresh token e, opcionalmente, o email são gravados. O MCP renova tokens automaticamente e salva o novo refresh token quando a API faz rotação.
80
+
81
+ O arquivo `~/.devvo-ops-mcp/auth.json` é uma credencial sensível: deve permanecer fora do repositório, nunca deve ser commitado, enviado ou compartilhado, e é removido no logout. O pacote solicita permissões `0700` para o diretório e `0600` para o arquivo quando o sistema operacional oferece suporte.
82
+
83
+ Comandos disponíveis:
84
+
85
+ ```bash
86
+ npx devvo-ops-mcp login
87
+ npx devvo-ops-mcp status
88
+ npx devvo-ops-mcp logout
89
+ ```
90
+
91
+ Executar o pacote sem subcomando inicia o servidor MCP via `stdio`.
92
+
93
+ ## Uso em um cliente MCP
94
+
95
+ Faça login primeiro e adicione uma configuração equivalente à seguinte no cliente:
96
+
97
+ ```json
98
+ {
99
+ "mcpServers": {
100
+ "devvo-ops": {
101
+ "command": "npx",
102
+ "args": ["-y", "devvo-ops-mcp"],
103
+ "env": {
104
+ "DEVVO_OPS_API_URL": "https://api.example.com"
105
+ }
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ Não coloque email, senha, access token ou refresh token nessa configuração.
112
+
113
+ Exemplos de solicitações:
114
+
115
+ ```text
116
+ Mostre a TASK-100.
117
+ O que precisa ser feito na TASK-100?
118
+ Leia a TASK-100 e me explique como você implementaria.
119
+ ```
120
+
121
+ ## MCP Inspector
122
+
123
+ Depois do build, teste o transporte e a tool com:
124
+
125
+ ```bash
126
+ npx @modelcontextprotocol/inspector node dist/index.js
127
+ ```
128
+
129
+ Também é possível inspecionar diretamente o código TypeScript durante o desenvolvimento:
130
+
131
+ ```bash
132
+ npx @modelcontextprotocol/inspector npx tsx src/index.ts
133
+ ```
134
+
135
+ ## Boas práticas para novas tools
136
+
137
+ Toda nova tool deve manter o fluxo:
138
+
139
+ ```text
140
+ Tool
141
+ ↓
142
+ ApiClient
143
+ ↓
144
+ API
145
+ ```
146
+
147
+ Nunca use:
148
+
149
+ ```text
150
+ Tool
151
+ ↓
152
+ Service interno
153
+ ```
154
+
155
+ As tools devem ter responsabilidade pequena e schemas claros, não conhecer autenticação, não acessar banco diretamente, não duplicar regras de negócio da API, retornar apenas contexto útil ao LLM e utilizar o `ApiClient` compartilhado.
@@ -0,0 +1,10 @@
1
+ import { AuthManager } from '../auth/auth-manager.js';
2
+ import { HttpClient } from './http-client.js';
3
+ import { type TaskForLlm } from './task-mapper.js';
4
+ export declare class ApiClient {
5
+ private readonly httpClient;
6
+ private readonly authManager;
7
+ constructor(httpClient: HttpClient, authManager: AuthManager);
8
+ getTaskByKey(key: string): Promise<TaskForLlm>;
9
+ private authenticatedRequest;
10
+ }
@@ -0,0 +1,40 @@
1
+ import { ApiUnavailableError, AuthenticationRequiredError, AuthorizationError, TaskNotFoundError, UnexpectedApiResponseError, } from '../errors.js';
2
+ import { mapTaskForLlm } from './task-mapper.js';
3
+ export class ApiClient {
4
+ httpClient;
5
+ authManager;
6
+ constructor(httpClient, authManager) {
7
+ this.httpClient = httpClient;
8
+ this.authManager = authManager;
9
+ }
10
+ async getTaskByKey(key) {
11
+ const path = `/tasks/by-key/${encodeURIComponent(key)}`;
12
+ let response = await this.authenticatedRequest(path);
13
+ if (response.status === 401) {
14
+ const accessToken = await this.authManager.refreshAccessToken();
15
+ response = await this.httpClient.request(path, { accessToken });
16
+ }
17
+ if (response.status === 401) {
18
+ await this.authManager.invalidateSession();
19
+ throw new AuthenticationRequiredError();
20
+ }
21
+ if (response.status === 403) {
22
+ throw new AuthorizationError();
23
+ }
24
+ if (response.status === 404) {
25
+ throw new TaskNotFoundError(key);
26
+ }
27
+ if (response.status >= 500) {
28
+ throw new ApiUnavailableError();
29
+ }
30
+ if (response.status < 200 || response.status >= 300) {
31
+ throw new UnexpectedApiResponseError();
32
+ }
33
+ return mapTaskForLlm(response.data);
34
+ }
35
+ async authenticatedRequest(path) {
36
+ const accessToken = await this.authManager.getAccessToken();
37
+ return this.httpClient.request(path, { accessToken });
38
+ }
39
+ }
40
+ //# sourceMappingURL=api-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api-client.js","sourceRoot":"","sources":["../../src/api/api-client.ts"],"names":[],"mappings":"AACA,OAAO,EACL,mBAAmB,EACnB,2BAA2B,EAC3B,kBAAkB,EAClB,iBAAiB,EACjB,0BAA0B,GAC3B,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAmB,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAElE,MAAM,OAAO,SAAS;IAED;IACA;IAFnB,YACmB,UAAsB,EACtB,WAAwB;QADxB,eAAU,GAAV,UAAU,CAAY;QACtB,gBAAW,GAAX,WAAW,CAAa;IACxC,CAAC;IAEJ,KAAK,CAAC,YAAY,CAAC,GAAW;QAC5B,MAAM,IAAI,GAAG,iBAAiB,kBAAkB,CAAC,GAAG,CAAC,EAAE,CAAC;QACxD,IAAI,QAAQ,GAAG,MAAM,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QAErD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,kBAAkB,EAAE,CAAC;YAChE,QAAQ,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,CAAC,WAAW,CAAC,iBAAiB,EAAE,CAAC;YAC3C,MAAM,IAAI,2BAA2B,EAAE,CAAC;QAC1C,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,kBAAkB,EAAE,CAAC;QACjC,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAC5B,MAAM,IAAI,iBAAiB,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YAC3B,MAAM,IAAI,mBAAmB,EAAE,CAAC;QAClC,CAAC;QACD,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YACpD,MAAM,IAAI,0BAA0B,EAAE,CAAC;QACzC,CAAC;QAED,OAAO,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IAEO,KAAK,CAAC,oBAAoB,CAAC,IAAY;QAC7C,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,EAAE,CAAC;QAC5D,OAAO,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;IACxD,CAAC;CACF"}
@@ -0,0 +1,20 @@
1
+ export type HttpResponse = {
2
+ status: number;
3
+ data: unknown;
4
+ };
5
+ export type HttpClientOptions = {
6
+ baseUrl: string;
7
+ timeoutMs?: number;
8
+ fetchImplementation?: typeof fetch;
9
+ };
10
+ export declare class HttpClient {
11
+ private readonly baseUrl;
12
+ private readonly timeoutMs;
13
+ private readonly fetchImplementation;
14
+ constructor(options: HttpClientOptions);
15
+ request(path: string, options?: {
16
+ method?: string;
17
+ body?: unknown;
18
+ accessToken?: string;
19
+ }): Promise<HttpResponse>;
20
+ }
@@ -0,0 +1,47 @@
1
+ import { ApiTimeoutError, ApiUnavailableError, UnexpectedApiResponseError } from '../errors.js';
2
+ export class HttpClient {
3
+ baseUrl;
4
+ timeoutMs;
5
+ fetchImplementation;
6
+ constructor(options) {
7
+ this.baseUrl = options.baseUrl.replace(/\/$/, '');
8
+ this.timeoutMs = options.timeoutMs ?? 10_000;
9
+ this.fetchImplementation = options.fetchImplementation ?? globalThis.fetch;
10
+ }
11
+ async request(path, options = {}) {
12
+ const headers = new Headers({ Accept: 'application/json' });
13
+ if (options.body !== undefined) {
14
+ headers.set('Content-Type', 'application/json');
15
+ }
16
+ if (options.accessToken) {
17
+ headers.set('Authorization', `Bearer ${options.accessToken}`);
18
+ }
19
+ let response;
20
+ try {
21
+ response = await this.fetchImplementation(new URL(path, `${this.baseUrl}/`), {
22
+ method: options.method ?? 'GET',
23
+ headers,
24
+ body: options.body === undefined ? undefined : JSON.stringify(options.body),
25
+ signal: AbortSignal.timeout(this.timeoutMs),
26
+ });
27
+ }
28
+ catch (error) {
29
+ if (error instanceof DOMException &&
30
+ (error.name === 'AbortError' || error.name === 'TimeoutError')) {
31
+ throw new ApiTimeoutError();
32
+ }
33
+ throw new ApiUnavailableError();
34
+ }
35
+ const responseText = await response.text();
36
+ if (!responseText) {
37
+ return { status: response.status, data: null };
38
+ }
39
+ try {
40
+ return { status: response.status, data: JSON.parse(responseText) };
41
+ }
42
+ catch {
43
+ throw new UnexpectedApiResponseError();
44
+ }
45
+ }
46
+ }
47
+ //# sourceMappingURL=http-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../../src/api/http-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAC;AAahG,MAAM,OAAO,UAAU;IACJ,OAAO,CAAS;IAChB,SAAS,CAAS;IAClB,mBAAmB,CAAe;IAEnD,YAAY,OAA0B;QACpC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAClD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAC;QAC7C,IAAI,CAAC,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,IAAI,UAAU,CAAC,KAAK,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,OAAO,CACX,IAAY,EACZ,UAAqE,EAAE;QAEvE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC5D,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC/B,OAAO,CAAC,GAAG,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;QAClD,CAAC;QACD,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACxB,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,UAAU,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;QAChE,CAAC;QAED,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,IAAI,CAAC,mBAAmB,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,OAAO,GAAG,CAAC,EAAE;gBAC3E,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;gBAC/B,OAAO;gBACP,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;gBAC3E,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;aAC5C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IACE,KAAK,YAAY,YAAY;gBAC7B,CAAC,KAAK,CAAC,IAAI,KAAK,YAAY,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,CAAC,EAC9D,CAAC;gBACD,MAAM,IAAI,eAAe,EAAE,CAAC;YAC9B,CAAC;YACD,MAAM,IAAI,mBAAmB,EAAE,CAAC;QAClC,CAAC;QAED,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC3C,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;QACjD,CAAC;QAED,IAAI,CAAC;YACH,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAY,EAAE,CAAC;QAChF,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,0BAA0B,EAAE,CAAC;QACzC,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,41 @@
1
+ export type TaskForLlm = {
2
+ key: string;
3
+ title: string;
4
+ description: string | null;
5
+ status: string;
6
+ priority: string;
7
+ type: string;
8
+ isEpic: boolean;
9
+ estimatedDuration: string | null;
10
+ assignee: {
11
+ name: string;
12
+ email: string;
13
+ } | null;
14
+ project: {
15
+ key: string;
16
+ name: string;
17
+ };
18
+ sprint: {
19
+ name: string;
20
+ status: string;
21
+ } | null;
22
+ taskType: string | null;
23
+ checklist: Array<{
24
+ text: string;
25
+ done: boolean;
26
+ }>;
27
+ tags: string[];
28
+ comments: TaskCommentForLlm[];
29
+ createdAt: string;
30
+ updatedAt: string;
31
+ };
32
+ export type TaskCommentForLlm = {
33
+ text: string;
34
+ author: {
35
+ name: string;
36
+ email: string;
37
+ };
38
+ createdAt: string;
39
+ replies: TaskCommentForLlm[];
40
+ };
41
+ export declare function mapTaskForLlm(data: unknown): TaskForLlm;
@@ -0,0 +1,78 @@
1
+ import { z } from 'zod';
2
+ import { UnexpectedApiResponseError } from '../errors.js';
3
+ const userSchema = z.object({
4
+ firstName: z.string(),
5
+ lastName: z.string(),
6
+ email: z.string().email(),
7
+ });
8
+ const commentSchema = z.lazy(() => z.object({
9
+ plainText: z.string(),
10
+ createdAt: z.string(),
11
+ author: userSchema,
12
+ replies: z.array(commentSchema).optional(),
13
+ }));
14
+ const apiTaskSchema = z.object({
15
+ issueKey: z.string(),
16
+ title: z.string(),
17
+ description: z.string().nullable().optional(),
18
+ status: z.string(),
19
+ priority: z.string(),
20
+ type: z.string(),
21
+ isEpic: z.boolean().optional(),
22
+ estimatedDurationInput: z.string().nullable().optional(),
23
+ assignee: userSchema.nullable().optional(),
24
+ project: z.object({ key: z.string(), name: z.string() }),
25
+ sprint: z.object({ name: z.string(), status: z.string() }).nullable().optional(),
26
+ taskType: z.object({ name: z.string() }).nullable().optional(),
27
+ checklist: z.array(z.object({ text: z.string(), isDone: z.boolean() })).optional(),
28
+ tags: z.array(z.object({ name: z.string() })).optional(),
29
+ comments: z.array(commentSchema).optional(),
30
+ createdAt: z.string(),
31
+ updatedAt: z.string(),
32
+ });
33
+ export function mapTaskForLlm(data) {
34
+ const parsed = apiTaskSchema.safeParse(data);
35
+ if (!parsed.success) {
36
+ throw new UnexpectedApiResponseError();
37
+ }
38
+ const task = parsed.data;
39
+ return {
40
+ key: task.issueKey,
41
+ title: task.title,
42
+ description: task.description ?? null,
43
+ status: task.status,
44
+ priority: task.priority,
45
+ type: task.type,
46
+ isEpic: task.isEpic ?? false,
47
+ estimatedDuration: task.estimatedDurationInput ?? null,
48
+ assignee: task.assignee
49
+ ? {
50
+ name: `${task.assignee.firstName} ${task.assignee.lastName}`.trim(),
51
+ email: task.assignee.email,
52
+ }
53
+ : null,
54
+ project: task.project,
55
+ sprint: task.sprint ?? null,
56
+ taskType: task.taskType?.name ?? null,
57
+ checklist: (task.checklist ?? []).map((item) => ({
58
+ text: item.text,
59
+ done: item.isDone,
60
+ })),
61
+ tags: (task.tags ?? []).map((tag) => tag.name),
62
+ comments: (task.comments ?? []).map(mapComment),
63
+ createdAt: task.createdAt,
64
+ updatedAt: task.updatedAt,
65
+ };
66
+ }
67
+ function mapComment(comment) {
68
+ return {
69
+ text: comment.plainText,
70
+ author: {
71
+ name: `${comment.author.firstName} ${comment.author.lastName}`.trim(),
72
+ email: comment.author.email,
73
+ },
74
+ createdAt: comment.createdAt,
75
+ replies: (comment.replies ?? []).map(mapComment),
76
+ };
77
+ }
78
+ //# sourceMappingURL=task-mapper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-mapper.js","sourceRoot":"","sources":["../../src/api/task-mapper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import { HttpClient } from '../api/http-client.js';
2
+ import { SessionStore } from './session-store.js';
3
+ export type AuthStatus = {
4
+ authenticated: boolean;
5
+ email?: string;
6
+ };
7
+ export declare class AuthManager {
8
+ private readonly httpClient;
9
+ private readonly sessionStore;
10
+ private accessToken;
11
+ private accessTokenExpiresAt;
12
+ private refreshInFlight;
13
+ constructor(httpClient: HttpClient, sessionStore: SessionStore);
14
+ login(email: string, password: string): Promise<void>;
15
+ logout(): Promise<void>;
16
+ getStatus(): Promise<AuthStatus>;
17
+ getAccessToken(): Promise<string>;
18
+ refreshAccessToken(): Promise<string>;
19
+ invalidateSession(): Promise<void>;
20
+ private performRefresh;
21
+ private setAccessToken;
22
+ private clearAccessToken;
23
+ private isAccessTokenExpiring;
24
+ }
@@ -0,0 +1,131 @@
1
+ import { z } from 'zod';
2
+ import { ApiUnavailableError, AuthenticationRequiredError, UnexpectedApiResponseError, } from '../errors.js';
3
+ const tokenPairSchema = z.object({
4
+ accessToken: z.string().min(1),
5
+ refreshToken: z.string().min(1),
6
+ });
7
+ export class AuthManager {
8
+ httpClient;
9
+ sessionStore;
10
+ accessToken = null;
11
+ accessTokenExpiresAt = null;
12
+ refreshInFlight = null;
13
+ constructor(httpClient, sessionStore) {
14
+ this.httpClient = httpClient;
15
+ this.sessionStore = sessionStore;
16
+ }
17
+ async login(email, password) {
18
+ const response = await this.httpClient.request('/auth/login', {
19
+ method: 'POST',
20
+ body: { email, password },
21
+ });
22
+ if (response.status === 401 || response.status === 403) {
23
+ throw new Error('Email ou senha inválidos.');
24
+ }
25
+ if (response.status >= 500) {
26
+ throw new ApiUnavailableError();
27
+ }
28
+ if (response.status < 200 || response.status >= 300) {
29
+ throw new UnexpectedApiResponseError();
30
+ }
31
+ const tokens = parseTokenPair(response.data);
32
+ this.setAccessToken(tokens.accessToken);
33
+ await this.sessionStore.saveSession({
34
+ refreshToken: tokens.refreshToken,
35
+ user: { email },
36
+ });
37
+ }
38
+ async logout() {
39
+ this.clearAccessToken();
40
+ await this.sessionStore.deleteSession();
41
+ }
42
+ async getStatus() {
43
+ const session = await this.sessionStore.getSession();
44
+ return session ? { authenticated: true, email: session.user?.email } : { authenticated: false };
45
+ }
46
+ async getAccessToken() {
47
+ if (this.accessToken && !this.isAccessTokenExpiring()) {
48
+ return this.accessToken;
49
+ }
50
+ return this.refreshAccessToken();
51
+ }
52
+ async refreshAccessToken() {
53
+ if (!this.refreshInFlight) {
54
+ this.refreshInFlight = this.performRefresh().finally(() => {
55
+ this.refreshInFlight = null;
56
+ });
57
+ }
58
+ return this.refreshInFlight;
59
+ }
60
+ async invalidateSession() {
61
+ this.clearAccessToken();
62
+ await this.sessionStore.deleteSession();
63
+ }
64
+ async performRefresh() {
65
+ const session = await this.sessionStore.getSession();
66
+ if (!session) {
67
+ throw new AuthenticationRequiredError();
68
+ }
69
+ const response = await this.httpClient.request('/auth/refresh', {
70
+ method: 'POST',
71
+ body: { refreshToken: session.refreshToken },
72
+ });
73
+ if (response.status === 401 || response.status === 403) {
74
+ await this.invalidateSession();
75
+ throw new AuthenticationRequiredError();
76
+ }
77
+ if (response.status >= 500) {
78
+ throw new ApiUnavailableError();
79
+ }
80
+ if (response.status < 200 || response.status >= 300) {
81
+ throw new UnexpectedApiResponseError();
82
+ }
83
+ let tokens;
84
+ try {
85
+ tokens = tokenPairSchema.parse(response.data);
86
+ }
87
+ catch {
88
+ throw new UnexpectedApiResponseError();
89
+ }
90
+ this.setAccessToken(tokens.accessToken);
91
+ await this.sessionStore.saveSession({
92
+ refreshToken: tokens.refreshToken,
93
+ user: session.user,
94
+ });
95
+ return tokens.accessToken;
96
+ }
97
+ setAccessToken(accessToken) {
98
+ this.accessToken = accessToken;
99
+ this.accessTokenExpiresAt = readJwtExpiration(accessToken);
100
+ }
101
+ clearAccessToken() {
102
+ this.accessToken = null;
103
+ this.accessTokenExpiresAt = null;
104
+ }
105
+ isAccessTokenExpiring() {
106
+ return this.accessTokenExpiresAt !== null && this.accessTokenExpiresAt <= Date.now() + 30_000;
107
+ }
108
+ }
109
+ function parseTokenPair(data) {
110
+ try {
111
+ return tokenPairSchema.parse(data);
112
+ }
113
+ catch {
114
+ throw new UnexpectedApiResponseError();
115
+ }
116
+ }
117
+ function readJwtExpiration(token) {
118
+ const payload = token.split('.')[1];
119
+ if (!payload) {
120
+ return null;
121
+ }
122
+ try {
123
+ const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
124
+ const parsed = z.object({ exp: z.number().positive() }).safeParse(decoded);
125
+ return parsed.success ? parsed.data.exp * 1000 : null;
126
+ }
127
+ catch {
128
+ return null;
129
+ }
130
+ }
131
+ //# sourceMappingURL=auth-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-manager.js","sourceRoot":"","sources":["../../src/auth/auth-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ declare const sessionSchema: z.ZodObject<{
3
+ refreshToken: z.ZodString;
4
+ user: z.ZodOptional<z.ZodObject<{
5
+ email: z.ZodString;
6
+ }, "strip", z.ZodTypeAny, {
7
+ email: string;
8
+ }, {
9
+ email: string;
10
+ }>>;
11
+ }, "strip", z.ZodTypeAny, {
12
+ refreshToken: string;
13
+ user?: {
14
+ email: string;
15
+ } | undefined;
16
+ }, {
17
+ refreshToken: string;
18
+ user?: {
19
+ email: string;
20
+ } | undefined;
21
+ }>;
22
+ export type Session = z.infer<typeof sessionSchema>;
23
+ export declare class SessionStore {
24
+ readonly directory: string;
25
+ readonly filePath: string;
26
+ constructor(directory?: string);
27
+ getSession(): Promise<Session | null>;
28
+ saveSession(session: Session): Promise<void>;
29
+ deleteSession(): Promise<void>;
30
+ }
31
+ export {};
@@ -0,0 +1,55 @@
1
+ import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { z } from 'zod';
5
+ const sessionSchema = z.object({
6
+ refreshToken: z.string().min(1),
7
+ user: z
8
+ .object({
9
+ email: z.string().email(),
10
+ })
11
+ .optional(),
12
+ });
13
+ export class SessionStore {
14
+ directory;
15
+ filePath;
16
+ constructor(directory = join(homedir(), '.devvo-ops-mcp')) {
17
+ this.directory = directory;
18
+ this.filePath = join(directory, 'auth.json');
19
+ }
20
+ async getSession() {
21
+ let contents;
22
+ try {
23
+ contents = await readFile(this.filePath, 'utf8');
24
+ }
25
+ catch (error) {
26
+ if (isFileSystemError(error, 'ENOENT')) {
27
+ return null;
28
+ }
29
+ throw error;
30
+ }
31
+ try {
32
+ return sessionSchema.parse(JSON.parse(contents));
33
+ }
34
+ catch {
35
+ throw new Error('O arquivo de sessão local é inválido. Execute "npx devvo-ops-mcp logout" e depois "npx devvo-ops-mcp login".');
36
+ }
37
+ }
38
+ async saveSession(session) {
39
+ const safeSession = sessionSchema.parse(session);
40
+ await mkdir(this.directory, { recursive: true, mode: 0o700 });
41
+ await chmod(this.directory, 0o700).catch(() => undefined);
42
+ await writeFile(this.filePath, `${JSON.stringify(safeSession, null, 2)}\n`, {
43
+ encoding: 'utf8',
44
+ mode: 0o600,
45
+ });
46
+ await chmod(this.filePath, 0o600).catch(() => undefined);
47
+ }
48
+ async deleteSession() {
49
+ await rm(this.filePath, { force: true });
50
+ }
51
+ }
52
+ function isFileSystemError(error, code) {
53
+ return error instanceof Error && 'code' in error && error.code === code;
54
+ }
55
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/auth/session-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACzE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,aAAa,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC;SACJ,MAAM,CAAC;QACN,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE;KAC1B,CAAC;SACD,QAAQ,EAAE;CACd,CAAC,CAAC;AAIH,MAAM,OAAO,YAAY;IACd,SAAS,CAAS;IAClB,QAAQ,CAAS;IAE1B,YAAY,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,gBAAgB,CAAC;QACvD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,UAAU;QACd,IAAI,QAAgB,CAAC;QACrB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,iBAAiB,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE,CAAC;gBACvC,OAAO,IAAI,CAAC;YACd,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QAED,IAAI,CAAC;YACH,OAAO,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QACnD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb,8GAA8G,CAC/G,CAAC;QACJ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAgB;QAChC,MAAM,WAAW,GAAG,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjD,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9D,MAAM,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC1D,MAAM,SAAS,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;YAC1E,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,KAAK;SACZ,CAAC,CAAC;QACH,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC3D,CAAC;IAED,KAAK,CAAC,aAAa;QACjB,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,CAAC;CACF;AAED,SAAS,iBAAiB,CAAC,KAAc,EAAE,IAAY;IACrD,OAAO,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;AAC1E,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function promptEmail(): Promise<string>;
2
+ export declare function promptPassword(): Promise<string>;
@@ -0,0 +1,59 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ export async function promptEmail() {
3
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
4
+ try {
5
+ return (await prompt.question('Email: ')).trim();
6
+ }
7
+ finally {
8
+ prompt.close();
9
+ }
10
+ }
11
+ export async function promptPassword() {
12
+ if (!process.stdin.isTTY || typeof process.stdin.setRawMode !== 'function') {
13
+ const prompt = createInterface({ input: process.stdin, output: process.stdout });
14
+ try {
15
+ return await prompt.question('Senha: ');
16
+ }
17
+ finally {
18
+ prompt.close();
19
+ }
20
+ }
21
+ process.stdout.write('Senha: ');
22
+ process.stdin.setRawMode(true);
23
+ process.stdin.resume();
24
+ process.stdin.setEncoding('utf8');
25
+ return new Promise((resolve, reject) => {
26
+ let password = '';
27
+ const finish = (error) => {
28
+ process.stdin.removeListener('data', onData);
29
+ process.stdin.setRawMode(false);
30
+ process.stdin.pause();
31
+ process.stdout.write('\n');
32
+ if (error) {
33
+ reject(error);
34
+ }
35
+ else {
36
+ resolve(password);
37
+ }
38
+ };
39
+ const onData = (chunk) => {
40
+ for (const character of chunk) {
41
+ if (character === '\u0003') {
42
+ finish(new Error('Login cancelado.'));
43
+ return;
44
+ }
45
+ if (character === '\r' || character === '\n') {
46
+ finish();
47
+ return;
48
+ }
49
+ if (character === '\u007f' || character === '\b') {
50
+ password = password.slice(0, -1);
51
+ continue;
52
+ }
53
+ password += character;
54
+ }
55
+ };
56
+ process.stdin.on('data', onData);
57
+ });
58
+ }
59
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjF,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACnD,CAAC;YAAS,CAAC;QACT,MAAM,CAAC,KAAK,EAAE,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,OAAO,OAAO,CAAC,KAAK,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QAC3E,MAAM,MAAM,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QACjF,IAAI,CAAC;YACH,OAAO,MAAM,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC;gBAAS,CAAC;YACT,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IAElC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,QAAQ,GAAG,EAAE,CAAC;QAElB,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE;YAC/B,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC7C,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAChC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAC3B,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,QAAQ,CAAC,CAAC;YACpB,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE;YAC/B,KAAK,MAAM,SAAS,IAAI,KAAK,EAAE,CAAC;gBAC9B,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;oBAC3B,MAAM,CAAC,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC;oBACtC,OAAO;gBACT,CAAC;gBACD,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBAC7C,MAAM,EAAE,CAAC;oBACT,OAAO;gBACT,CAAC;gBACD,IAAI,SAAS,KAAK,QAAQ,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBACjD,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;oBACjC,SAAS;gBACX,CAAC;gBACD,QAAQ,IAAI,SAAS,CAAC;YACxB,CAAC;QACH,CAAC,CAAC;QAEF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function getApiBaseUrl(environment?: NodeJS.ProcessEnv): string;
package/dist/config.js ADDED
@@ -0,0 +1,15 @@
1
+ const DEFAULT_API_URL = 'http://localhost:3334';
2
+ export function getApiBaseUrl(environment = process.env) {
3
+ const configuredUrl = environment.DEVVO_OPS_API_URL?.trim() || DEFAULT_API_URL;
4
+ try {
5
+ const url = new URL(configuredUrl);
6
+ if (!['http:', 'https:'].includes(url.protocol)) {
7
+ throw new Error('unsupported protocol');
8
+ }
9
+ return url.toString().replace(/\/$/, '');
10
+ }
11
+ catch {
12
+ throw new Error('DEVVO_OPS_API_URL deve ser uma URL HTTP(S) válida.');
13
+ }
14
+ }
15
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD,MAAM,UAAU,aAAa,CAAC,cAAiC,OAAO,CAAC,GAAG;IACxE,MAAM,aAAa,GAAG,WAAW,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,eAAe,CAAC;IAE/E,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,aAAa,CAAC,CAAC;QACnC,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;AACH,CAAC"}
@@ -0,0 +1,21 @@
1
+ export declare class McpClientError extends Error {
2
+ constructor(message: string);
3
+ }
4
+ export declare class AuthenticationRequiredError extends McpClientError {
5
+ constructor();
6
+ }
7
+ export declare class AuthorizationError extends McpClientError {
8
+ constructor();
9
+ }
10
+ export declare class TaskNotFoundError extends McpClientError {
11
+ constructor(key: string);
12
+ }
13
+ export declare class ApiUnavailableError extends McpClientError {
14
+ constructor();
15
+ }
16
+ export declare class ApiTimeoutError extends McpClientError {
17
+ constructor();
18
+ }
19
+ export declare class UnexpectedApiResponseError extends McpClientError {
20
+ constructor();
21
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,37 @@
1
+ export class McpClientError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = new.target.name;
5
+ }
6
+ }
7
+ export class AuthenticationRequiredError extends McpClientError {
8
+ constructor() {
9
+ super('Autenticação necessária. Execute "npx devvo-ops-mcp login" e tente novamente.');
10
+ }
11
+ }
12
+ export class AuthorizationError extends McpClientError {
13
+ constructor() {
14
+ super('Você não tem permissão para acessar esta task.');
15
+ }
16
+ }
17
+ export class TaskNotFoundError extends McpClientError {
18
+ constructor(key) {
19
+ super(`Task ${key} não encontrada.`);
20
+ }
21
+ }
22
+ export class ApiUnavailableError extends McpClientError {
23
+ constructor() {
24
+ super('A API do Devvo Ops está indisponível. Tente novamente mais tarde.');
25
+ }
26
+ }
27
+ export class ApiTimeoutError extends McpClientError {
28
+ constructor() {
29
+ super('A API do Devvo Ops não respondeu dentro do tempo limite.');
30
+ }
31
+ }
32
+ export class UnexpectedApiResponseError extends McpClientError {
33
+ constructor() {
34
+ super('A API do Devvo Ops retornou uma resposta inesperada.');
35
+ }
36
+ }
37
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;IAC9B,CAAC;CACF;AAED,MAAM,OAAO,2BAA4B,SAAQ,cAAc;IAC7D;QACE,KAAK,CAAC,+EAA+E,CAAC,CAAC;IACzF,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,cAAc;IACpD;QACE,KAAK,CAAC,gDAAgD,CAAC,CAAC;IAC1D,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,cAAc;IACnD,YAAY,GAAW;QACrB,KAAK,CAAC,QAAQ,GAAG,kBAAkB,CAAC,CAAC;IACvC,CAAC;CACF;AAED,MAAM,OAAO,mBAAoB,SAAQ,cAAc;IACrD;QACE,KAAK,CAAC,mEAAmE,CAAC,CAAC;IAC7E,CAAC;CACF;AAED,MAAM,OAAO,eAAgB,SAAQ,cAAc;IACjD;QACE,KAAK,CAAC,0DAA0D,CAAC,CAAC;IACpE,CAAC;CACF;AAED,MAAM,OAAO,0BAA2B,SAAQ,cAAc;IAC5D;QACE,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAChE,CAAC;CACF"}
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { ApiClient } from './api/api-client.js';
4
+ export declare function createServer(apiClient: ApiClient): McpServer;
5
+ export declare function main(args?: string[]): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from 'node:url';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { ApiClient } from './api/api-client.js';
6
+ import { HttpClient } from './api/http-client.js';
7
+ import { AuthManager } from './auth/auth-manager.js';
8
+ import { SessionStore } from './auth/session-store.js';
9
+ import { promptEmail, promptPassword } from './cli/prompts.js';
10
+ import { getApiBaseUrl } from './config.js';
11
+ import { createGetTaskByKeyHandler, getTaskByKeyInputSchema } from './tools/get-task-by-key.js';
12
+ const VERSION = '0.1.0';
13
+ function createDependencies() {
14
+ const httpClient = new HttpClient({ baseUrl: getApiBaseUrl() });
15
+ const sessionStore = new SessionStore();
16
+ const authManager = new AuthManager(httpClient, sessionStore);
17
+ const apiClient = new ApiClient(httpClient, authManager);
18
+ return { apiClient, authManager, sessionStore };
19
+ }
20
+ export function createServer(apiClient) {
21
+ const server = new McpServer({ name: 'devvo-ops-mcp', version: VERSION });
22
+ server.registerTool('get_task_by_key', {
23
+ description: 'Busca uma task do gerenciador de projetos Devvo Ops através de sua key, como TASK-100.',
24
+ inputSchema: getTaskByKeyInputSchema,
25
+ }, createGetTaskByKeyHandler(apiClient));
26
+ return server;
27
+ }
28
+ export async function main(args = process.argv.slice(2)) {
29
+ const { apiClient, authManager, sessionStore } = createDependencies();
30
+ const command = args[0];
31
+ if (command === 'login') {
32
+ const email = await promptEmail();
33
+ let password = await promptPassword();
34
+ try {
35
+ await authManager.login(email, password);
36
+ process.stdout.write(`Login realizado. Sessão salva em ${sessionStore.filePath}.\n`);
37
+ }
38
+ finally {
39
+ password = '';
40
+ }
41
+ return;
42
+ }
43
+ if (command === 'logout') {
44
+ await authManager.logout();
45
+ process.stdout.write('Sessão local removida.\n');
46
+ return;
47
+ }
48
+ if (command === 'status') {
49
+ const status = await authManager.getStatus();
50
+ process.stdout.write(status.authenticated
51
+ ? `Autenticado${status.email ? ` como ${status.email}` : ''}.\n`
52
+ : 'Não autenticado. Execute "npx devvo-ops-mcp login".\n');
53
+ return;
54
+ }
55
+ if (command) {
56
+ throw new Error(`Comando desconhecido: ${command}. Use login, logout ou status.`);
57
+ }
58
+ const server = createServer(apiClient);
59
+ const transport = new StdioServerTransport();
60
+ await server.connect(transport);
61
+ }
62
+ const executablePath = process.argv[1];
63
+ if (executablePath && import.meta.url === pathToFileURL(executablePath).href) {
64
+ main().catch((error) => {
65
+ const message = error instanceof Error ? error.message : 'Erro inesperado.';
66
+ console.error(message);
67
+ process.exitCode = 1;
68
+ });
69
+ }
70
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ import type { ApiClient } from '../api/api-client.js';
3
+ export declare const getTaskByKeyInputSchema: {
4
+ key: z.ZodString;
5
+ };
6
+ export declare function createGetTaskByKeyHandler(apiClient: ApiClient): ({ key }: {
7
+ key: string;
8
+ }) => Promise<{
9
+ content: {
10
+ type: "text";
11
+ text: string;
12
+ }[];
13
+ structuredContent: import("../api/task-mapper.js").TaskForLlm;
14
+ isError?: undefined;
15
+ } | {
16
+ content: {
17
+ type: "text";
18
+ text: string;
19
+ }[];
20
+ isError: boolean;
21
+ structuredContent?: undefined;
22
+ }>;
@@ -0,0 +1,26 @@
1
+ import { z } from 'zod';
2
+ export const getTaskByKeyInputSchema = {
3
+ key: z
4
+ .string()
5
+ .trim()
6
+ .regex(/^[A-Za-z][A-Za-z0-9]*-\d+$/, 'Use uma key como TASK-100.'),
7
+ };
8
+ export function createGetTaskByKeyHandler(apiClient) {
9
+ return async ({ key }) => {
10
+ try {
11
+ const task = await apiClient.getTaskByKey(key.toUpperCase());
12
+ return {
13
+ content: [{ type: 'text', text: JSON.stringify(task, null, 2) }],
14
+ structuredContent: task,
15
+ };
16
+ }
17
+ catch (error) {
18
+ const message = error instanceof Error ? error.message : 'Não foi possível buscar a task.';
19
+ return {
20
+ content: [{ type: 'text', text: message }],
21
+ isError: true,
22
+ };
23
+ }
24
+ };
25
+ }
26
+ //# sourceMappingURL=get-task-by-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-task-by-key.js","sourceRoot":"","sources":["../../src/tools/get-task-by-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,GAAG,EAAE,CAAC;SACH,MAAM,EAAE;SACR,IAAI,EAAE;SACN,KAAK,CAAC,4BAA4B,EAAE,4BAA4B,CAAC;CACrE,CAAC;AAEF,MAAM,UAAU,yBAAyB,CAAC,SAAoB;IAC5D,OAAO,KAAK,EAAE,EAAE,GAAG,EAAmB,EAAE,EAAE;QACxC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;YAC7D,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;gBACzE,iBAAiB,EAAE,IAAI;aACxB,CAAC;QACJ,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,iCAAiC,CAAC;YAC3F,OAAO;gBACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;gBACnD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "devvo-ops-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Servidor MCP local para consultar o Devvo Ops.",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "bin": {
8
+ "devvo-ops-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=20"
16
+ },
17
+ "scripts": {
18
+ "dev": "tsx src/index.ts",
19
+ "build": "tsc -p tsconfig.json",
20
+ "start": "node dist/index.js",
21
+ "test": "vitest run",
22
+ "lint": "tsc -p tsconfig.json --noEmit",
23
+ "prepublishOnly": "npm run build && npm test"
24
+ },
25
+ "dependencies": {
26
+ "@modelcontextprotocol/sdk": "1.30.0",
27
+ "zod": "^3.25.76"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^22.10.7",
31
+ "tsx": "^4.21.0",
32
+ "typescript": "^5.7.3",
33
+ "vitest": "^3.2.4"
34
+ }
35
+ }