@taiga-ai/mcp-server 0.25.0-rc.474 → 0.26.0-dev.479

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +111 -0
  2. package/dist/index.js +292 -115
  3. package/package.json +15 -2
  4. package/scripts/build.mjs +0 -37
  5. package/scripts/prepare-publish.cjs +0 -49
  6. package/src/client.test.ts +0 -424
  7. package/src/client.ts +0 -977
  8. package/src/config.test.ts +0 -24
  9. package/src/config.ts +0 -28
  10. package/src/index.ts +0 -45
  11. package/src/modern.integration.test.ts +0 -153
  12. package/src/server.integration.test.ts +0 -9214
  13. package/src/server.ts +0 -18
  14. package/src/tools/annotations.ts +0 -38
  15. package/src/tools/autonomy.ts +0 -86
  16. package/src/tools/backlogs.ts +0 -802
  17. package/src/tools/builds.ts +0 -668
  18. package/src/tools/catalogue.test.ts +0 -170
  19. package/src/tools/catalogue.ts +0 -223
  20. package/src/tools/context.ts +0 -513
  21. package/src/tools/documents.ts +0 -375
  22. package/src/tools/errors.ts +0 -172
  23. package/src/tools/hints.ts +0 -99
  24. package/src/tools/inbox.ts +0 -546
  25. package/src/tools/index.ts +0 -239
  26. package/src/tools/inputs.ts +0 -47
  27. package/src/tools/instructions.ts +0 -41
  28. package/src/tools/jobs.ts +0 -90
  29. package/src/tools/knowledge.ts +0 -249
  30. package/src/tools/maintenance.ts +0 -547
  31. package/src/tools/markdown.test.ts +0 -249
  32. package/src/tools/markdown.ts +0 -151
  33. package/src/tools/observe.ts +0 -311
  34. package/src/tools/product-documents.ts +0 -184
  35. package/src/tools/products.ts +0 -529
  36. package/src/tools/prompts.ts +0 -265
  37. package/src/tools/queue.ts +0 -250
  38. package/src/tools/readiness.ts +0 -177
  39. package/src/tools/remote.test.ts +0 -44
  40. package/src/tools/remote.ts +0 -107
  41. package/src/tools/repositories.ts +0 -360
  42. package/src/tools/request-prompt.test.ts +0 -32
  43. package/src/tools/request-prompt.ts +0 -28
  44. package/src/tools/resources.ts +0 -217
  45. package/src/tools/sections.test.ts +0 -143
  46. package/src/tools/sections.ts +0 -287
  47. package/src/tools/setup.ts +0 -455
  48. package/src/tools/summarize.test.ts +0 -20
  49. package/src/tools/summarize.ts +0 -96
  50. package/src/tools/wait.ts +0 -490
  51. package/src/version.test.ts +0 -10
  52. package/src/version.ts +0 -14
  53. package/taiga-ai-mcp-server-0.25.0-rc.474.tgz +0 -0
  54. package/tsconfig.json +0 -21
  55. package/vitest.config.ts +0 -9
@@ -1,24 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
-
3
- import { readConfig } from './config.js';
4
-
5
- afterEach(() => {
6
- vi.unstubAllEnvs();
7
- });
8
-
9
- describe('readConfig', () => {
10
- it('needs an API key', () => {
11
- vi.stubEnv('TAIGA_API_KEY', undefined);
12
- expect(readConfig()).toBeNull();
13
- vi.stubEnv('TAIGA_API_KEY', '');
14
- expect(readConfig()).toBeNull();
15
- });
16
-
17
- it('defaults the API URL', () => {
18
- vi.stubEnv('TAIGA_API_KEY', 'taiga_sk_live_test');
19
- vi.stubEnv('TAIGA_API_URL', undefined);
20
- expect(readConfig()).toEqual({ apiKey: 'taiga_sk_live_test', apiUrl: 'https://api.eu.tai.ga' });
21
- vi.stubEnv('TAIGA_API_URL', 'http://localhost:3001');
22
- expect(readConfig()?.apiUrl).toBe('http://localhost:3001');
23
- });
24
- });
package/src/config.ts DELETED
@@ -1,28 +0,0 @@
1
- import { z } from 'zod';
2
-
3
- /**
4
- * The one place the MCP server reads its environment (PRO-2511), parsed once
5
- * at startup.
6
- */
7
-
8
- // The production API behind the custom domain in infra/dns.tf. The old
9
- // default, api.taiga.app, never resolved, so every install that followed the
10
- // README without TAIGA_API_URL failed on its first call.
11
- const DEFAULT_API_URL = 'https://api.eu.tai.ga';
12
-
13
- const envSchema = z.object({
14
- TAIGA_API_KEY: z.string().min(1),
15
- TAIGA_API_URL: z.string().default(DEFAULT_API_URL),
16
- });
17
-
18
- export interface ServerConfig {
19
- readonly apiKey: string;
20
- readonly apiUrl: string;
21
- }
22
-
23
- /** Null when TAIGA_API_KEY is missing or empty: it is the one setting with no default. */
24
- export function readConfig(): ServerConfig | null {
25
- const parsed = envSchema.safeParse(process.env);
26
- if (!parsed.success) return null;
27
- return { apiKey: parsed.data.TAIGA_API_KEY, apiUrl: parsed.data.TAIGA_API_URL };
28
- }
package/src/index.ts DELETED
@@ -1,45 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { serveStdio } from '@modelcontextprotocol/server/stdio';
4
- import { TaigaClient } from './client.js';
5
- import { readConfig } from './config.js';
6
- import { createServer } from './server.js';
7
-
8
- async function main() {
9
- const config = readConfig();
10
- if (!config) {
11
- console.error('Error: TAIGA_API_KEY environment variable is required.');
12
- console.error('Create one at https://eu.tai.ga under Settings > Account > API keys.');
13
- process.exit(1);
14
- }
15
-
16
- const client = new TaigaClient({ baseUrl: config.apiUrl, apiKey: config.apiKey });
17
-
18
- // Introspect the API key to discover available capabilities
19
- const introspection = await client.introspect().catch((error) => {
20
- const message = error instanceof Error ? error.message : String(error);
21
- console.error(`Error: Failed to validate API key: ${message}`);
22
- console.error('Check that your TAIGA_API_KEY is valid and not expired.');
23
- process.exit(1);
24
- });
25
-
26
- const toolCount = introspection.capabilities.length;
27
- console.error(
28
- `Taiga MCP server started (${toolCount} capabilities, user: ${introspection.email}, role: ${introspection.role})`
29
- );
30
-
31
- // serveStdio owns the protocol revision: a client that opens with
32
- // server/discover is served 2026-07-28, one that opens with initialize the
33
- // 2025 revision it asks for, from the same instance. Tools are registered
34
- // from the key's capabilities, so a key never sees a tool it cannot use.
35
- // Errors outside a request (the stream failing, a message that cannot be
36
- // read) go to stderr, which the client shows in its server log.
37
- serveStdio(() => createServer(client), {
38
- onerror: (error) => console.error('Taiga MCP server error:', error),
39
- });
40
- }
41
-
42
- main().catch((error) => {
43
- console.error('Fatal error:', error);
44
- process.exit(1);
45
- });
@@ -1,153 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
- import { Client, InMemoryTransport } from '@modelcontextprotocol/client';
3
- import { serveStdio } from '@modelcontextprotocol/server/stdio';
4
- import { TaigaClient } from './client.js';
5
- import { createServer } from './server.js';
6
-
7
- /**
8
- * The same server over the 2026-07-28 revision. serveStdio picks the
9
- * revision from the client's opening exchange, so a client pinned to
10
- * 2026-07-28 opens with server/discover and every call after it carries the
11
- * modern envelope. server.integration.test.ts covers the tools in depth over
12
- * the 2025 handshake; this proves the catalogue, a read, a write, the server
13
- * instructions, and a wait's progress and cancellation on the new revision.
14
- */
15
-
16
- const ITEM_ID = '0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5e';
17
- const PRODUCT_ID = '0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5c';
18
- const JOB_ID = '0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4b10';
19
-
20
- let close: (() => Promise<void>) | undefined;
21
-
22
- const json = (body: unknown, status = 200) =>
23
- new Response(JSON.stringify(body), { status, headers: { 'Content-Type': 'application/json' } });
24
-
25
- async function bootModern(capabilities: string[], routes: Record<string, (url: string, init: RequestInit) => unknown>) {
26
- vi.stubGlobal(
27
- 'fetch',
28
- vi.fn(async (url: string, init: RequestInit = {}) => {
29
- const { pathname } = new URL(url);
30
- if (pathname === '/v1/auth/introspect') {
31
- return json({ data: { userId: 'u', tenantId: 't', email: 'dev@test.com', role: 'member', capabilities } });
32
- }
33
- const route = routes[`${init.method ?? 'GET'} ${pathname}`];
34
- return route ? route(url, init) : json({ code: 'NOT_FOUND', message: 'No route' }, 404);
35
- })
36
- );
37
- const taiga = new TaigaClient({ baseUrl: 'https://api.test.com', apiKey: 'taiga_sk_live_test' });
38
- await taiga.introspect();
39
-
40
- const [clientSide, serverSide] = InMemoryTransport.createLinkedPair();
41
- const handle = serveStdio(() => createServer(taiga, { wait: { pollIntervalMs: 5 } }), { transport: serverSide });
42
- const client = new Client(
43
- { name: 'modern-test', version: '0.0.1' },
44
- { versionNegotiation: { mode: { pin: '2026-07-28' } } }
45
- );
46
- await client.connect(clientSide);
47
- close = async () => {
48
- await client.close();
49
- await handle.close();
50
- };
51
- return { client };
52
- }
53
- afterEach(async () => {
54
- await close?.();
55
- close = undefined;
56
- vi.unstubAllGlobals();
57
- });
58
-
59
- describe('the server on the 2026-07-28 revision', () => {
60
- it('negotiates 2026-07-28 and lists the same catalogue, with the server instructions', async () => {
61
- const { client } = await bootModern(['products:read', 'backlog:read', 'backlog:write', 'products:write'], {});
62
-
63
- expect(client.getProtocolEra()).toBe('modern');
64
- expect(client.getInstructions()).toContain('Taiga');
65
- const names = (await client.listTools()).tools.map((t) => t.name);
66
- expect(names).toContain('taiga_get_product');
67
- expect(names).toContain('taiga_request_initiatives');
68
- });
69
-
70
- it('reads, writes and refuses an unknown argument as it does on the 2025 revision', async () => {
71
- let posted: unknown;
72
- const { client } = await bootModern(['products:read', 'products:write'], {
73
- [`GET /v1/products/${PRODUCT_ID}`]: () => json({ data: { id: PRODUCT_ID, name: 'Web', slug: 'web' } }),
74
- 'POST /v1/agent-jobs': (_url, init) => {
75
- posted = JSON.parse(init.body as string);
76
- return json({ data: { id: JOB_ID, type: 'generate_backlog', status: 'pending' } });
77
- },
78
- });
79
-
80
- const product = await client.callTool({ name: 'taiga_get_product', arguments: { productId: PRODUCT_ID } });
81
- const requested = await client.callTool({
82
- name: 'taiga_request_initiatives',
83
- arguments: { productId: PRODUCT_ID, title: 'Health endpoint' },
84
- });
85
- const stray = await client.callTool({
86
- name: 'taiga_get_product',
87
- arguments: { productId: PRODUCT_ID, note: 'not an argument' },
88
- });
89
-
90
- expect(JSON.parse((product.content as { text: string }[])[0]!.text)).toMatchObject({ id: PRODUCT_ID, name: 'Web' });
91
- expect(posted).toMatchObject({ scope: 'product', type: 'generate_backlog', targetId: PRODUCT_ID });
92
- expect(JSON.parse((requested.content as { text: string }[])[0]!.text)).toMatchObject({ id: JOB_ID });
93
- expect(stray.isError).toBe(true);
94
- expect((stray.content as { text: string }[])[0]!.text).toMatch(/Unrecognized key/);
95
- });
96
-
97
- it('reports progress while a wait runs, and ends the read in flight when the call is cancelled', async () => {
98
- let reads = 0;
99
- let hung: AbortSignal | undefined;
100
- const job = (status: string) => json({ data: { id: JOB_ID, type: 'refresh_reality', status } });
101
- const { client } = await bootModern(['products:read', 'backlog:read'], {
102
- [`GET /v1/agent-jobs/${JOB_ID}`]: () => job(++reads < 3 ? 'processing' : 'completed'),
103
- [`GET /v1/backlog/${ITEM_ID}`]: (_url, init) => {
104
- hung = init.signal!;
105
- return new Promise((_resolve, reject) =>
106
- init.signal!.addEventListener('abort', () => reject(init.signal!.reason))
107
- );
108
- },
109
- });
110
- const onprogress = vi.fn();
111
-
112
- const waited = await client.callTool({ name: 'taiga_wait_for_job', arguments: { jobId: JOB_ID } }, { onprogress });
113
- expect(JSON.parse((waited.content as { text: string }[])[0]!.text)).toMatchObject({ reached: true });
114
- expect(onprogress).toHaveBeenCalledWith(expect.objectContaining({ message: 'Still waiting' }));
115
-
116
- const controller = new AbortController();
117
- const pending = client.callTool(
118
- { name: 'taiga_wait_for_initiative', arguments: { initiativeId: ITEM_ID, until: 'planned', timeoutSeconds: 60 } },
119
- { signal: controller.signal }
120
- );
121
- await vi.waitFor(() => expect(hung).toBeDefined());
122
- controller.abort();
123
- await expect(pending).rejects.toThrow();
124
- await vi.waitFor(() => expect(hung!.aborted).toBe(true));
125
- });
126
-
127
- it('reads a resource, gets a prompt and refuses a missing document with invalid params', async () => {
128
- const policy = {
129
- id: 'doc-1',
130
- type: 'policy_security',
131
- name: 'Security Policy',
132
- version: 3,
133
- isDraft: false,
134
- data: { version: '1.0.0', sections: { summary: { source: 'template', priority: 'core', content: 'Encrypt' } } },
135
- };
136
- const { client } = await bootModern(['tenant:documents:read'], {
137
- 'GET /v1/tenants/current/documents/policy_security': () => json({ data: policy }),
138
- 'GET /v1/tenants/current/documents': () => json({ data: [policy], nextCursor: null }),
139
- });
140
-
141
- const { contents } = await client.readResource({ uri: 'taiga://documents/policy_security' });
142
- const prompt = await client.getPrompt({
143
- name: 'taiga_review_against_policies',
144
- arguments: { policyType: 'policy_security' },
145
- });
146
- const missing = await client.readResource({ uri: 'taiga://documents/tone_of_voice' }).catch((e: unknown) => e);
147
-
148
- expect((contents[0] as { text: string }).text).toContain('# Security Policy');
149
- expect((prompt.messages[0]!.content as { text: string }).text).toContain('# Security Policy');
150
- expect(missing).toMatchObject({ code: -32602 });
151
- expect(String((missing as Error).message)).toMatch(/^No published tone_of_voice/);
152
- });
153
- });