@atoms-tech/atoms-mcp 0.3.0 → 0.3.1
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 +1 -1
- package/dist/index.cjs +2 -0
- package/dist/index.js +1 -90
- package/package.json +12 -3
- package/dist/apps/register.d.ts +0 -36
- package/dist/apps/register.js +0 -65
- package/dist/auth/login.d.ts +0 -24
- package/dist/auth/login.js +0 -413
- package/dist/auth/refresh.d.ts +0 -16
- package/dist/auth/refresh.js +0 -81
- package/dist/auth/token-store.d.ts +0 -33
- package/dist/auth/token-store.js +0 -60
- package/dist/config.d.ts +0 -18
- package/dist/config.js +0 -18
- package/dist/db/client.d.ts +0 -29
- package/dist/db/client.js +0 -109
- package/dist/db/graph.d.ts +0 -7
- package/dist/db/graph.js +0 -7
- package/dist/db/queries.d.ts +0 -76
- package/dist/db/queries.js +0 -209
- package/dist/index.d.ts +0 -11
- package/dist/middleware/audit.d.ts +0 -25
- package/dist/middleware/audit.js +0 -43
- package/dist/middleware/rate-limiter.d.ts +0 -20
- package/dist/middleware/rate-limiter.js +0 -42
- package/dist/middleware/validator.d.ts +0 -21
- package/dist/middleware/validator.js +0 -90
- package/dist/server.d.ts +0 -14
- package/dist/server.js +0 -679
- package/dist/tools/_base.d.ts +0 -57
- package/dist/tools/_base.js +0 -108
- package/dist/tools/bulk-import.d.ts +0 -69
- package/dist/tools/bulk-import.js +0 -187
- package/dist/tools/create-item.d.ts +0 -42
- package/dist/tools/create-item.js +0 -117
- package/dist/tools/delete-item.d.ts +0 -37
- package/dist/tools/delete-item.js +0 -68
- package/dist/tools/export-mermaid.d.ts +0 -35
- package/dist/tools/export-mermaid.js +0 -124
- package/dist/tools/get-coverage.d.ts +0 -33
- package/dist/tools/get-coverage.js +0 -35
- package/dist/tools/get-history.d.ts +0 -33
- package/dist/tools/get-history.js +0 -52
- package/dist/tools/get-item.d.ts +0 -61
- package/dist/tools/get-item.js +0 -92
- package/dist/tools/link-items.d.ts +0 -40
- package/dist/tools/link-items.js +0 -149
- package/dist/tools/list-items.d.ts +0 -36
- package/dist/tools/list-items.js +0 -35
- package/dist/tools/list-projects.d.ts +0 -37
- package/dist/tools/list-projects.js +0 -27
- package/dist/tools/project-summary.d.ts +0 -63
- package/dist/tools/project-summary.js +0 -169
- package/dist/tools/record-test-result.d.ts +0 -40
- package/dist/tools/record-test-result.js +0 -79
- package/dist/tools/search.d.ts +0 -33
- package/dist/tools/search.js +0 -27
- package/dist/tools/trace.d.ts +0 -52
- package/dist/tools/trace.js +0 -165
- package/dist/tools/update-item.d.ts +0 -42
- package/dist/tools/update-item.js +0 -97
- package/dist/types/responses.d.ts +0 -57
- package/dist/types/responses.js +0 -5
- package/dist/types/work-item.d.ts +0 -68
- package/dist/types/work-item.js +0 -7
package/dist/tools/_base.d.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared error handling and response formatting for all MCP tools.
|
|
3
|
-
*
|
|
4
|
-
* Every error MUST include `next_steps` — actionable guidance for the LLM.
|
|
5
|
-
* This is the #1 pattern from the Polarion MCP that improves agent reliability.
|
|
6
|
-
*/
|
|
7
|
-
import type { ToolError, ToolSuccess, PaginationMeta } from "../types/responses.js";
|
|
8
|
-
/** Maximum response size in characters. Truncate with clear message if exceeded. */
|
|
9
|
-
export declare const CHARACTER_LIMIT = 25000;
|
|
10
|
-
export declare function success<T>(data: T, meta?: PaginationMeta): ToolSuccess<T>;
|
|
11
|
-
export declare function paginationMeta(totalCount: number, limit: number, offset: number): PaginationMeta;
|
|
12
|
-
export declare function errorResponse(message: string, nextSteps: string[]): ToolError;
|
|
13
|
-
export declare function notFoundError(resource: string, id: string): ToolError;
|
|
14
|
-
export declare function accessDeniedError(role: string): ToolError;
|
|
15
|
-
export declare function validationError(message: string): ToolError;
|
|
16
|
-
export declare function authError(): ToolError;
|
|
17
|
-
export declare function rateLimitError(retryAfterSeconds: number): ToolError;
|
|
18
|
-
export declare function dbError(message: string): ToolError;
|
|
19
|
-
/**
|
|
20
|
-
* Format a tool result as JSON string + structuredContent for the MCP SDK.
|
|
21
|
-
* Applies CHARACTER_LIMIT truncation if needed.
|
|
22
|
-
*/
|
|
23
|
-
export declare function formatToolResult<T>(result: ToolSuccess<T> | ToolError): {
|
|
24
|
-
content: {
|
|
25
|
-
type: "text";
|
|
26
|
-
text: string;
|
|
27
|
-
}[];
|
|
28
|
-
structuredContent: {
|
|
29
|
-
data: any[];
|
|
30
|
-
meta: {
|
|
31
|
-
truncated: boolean;
|
|
32
|
-
truncation_message: string;
|
|
33
|
-
total_count?: number | undefined;
|
|
34
|
-
limit?: number | undefined;
|
|
35
|
-
offset?: number | undefined;
|
|
36
|
-
has_more?: boolean | undefined;
|
|
37
|
-
};
|
|
38
|
-
status: "success";
|
|
39
|
-
};
|
|
40
|
-
} | {
|
|
41
|
-
content: {
|
|
42
|
-
type: "text";
|
|
43
|
-
text: string;
|
|
44
|
-
}[];
|
|
45
|
-
structuredContent: ToolError | ToolSuccess<T>;
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Format an error result for the MCP SDK.
|
|
49
|
-
*/
|
|
50
|
-
export declare function formatErrorResult(error: ToolError): {
|
|
51
|
-
content: {
|
|
52
|
-
type: "text";
|
|
53
|
-
text: string;
|
|
54
|
-
}[];
|
|
55
|
-
structuredContent: ToolError;
|
|
56
|
-
isError: boolean;
|
|
57
|
-
};
|
package/dist/tools/_base.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared error handling and response formatting for all MCP tools.
|
|
3
|
-
*
|
|
4
|
-
* Every error MUST include `next_steps` — actionable guidance for the LLM.
|
|
5
|
-
* This is the #1 pattern from the Polarion MCP that improves agent reliability.
|
|
6
|
-
*/
|
|
7
|
-
/** Maximum response size in characters. Truncate with clear message if exceeded. */
|
|
8
|
-
export const CHARACTER_LIMIT = 25_000;
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Success helpers
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
export function success(data, meta) {
|
|
13
|
-
return { status: "success", data, ...(meta ? { meta } : {}) };
|
|
14
|
-
}
|
|
15
|
-
export function paginationMeta(totalCount, limit, offset) {
|
|
16
|
-
return {
|
|
17
|
-
total_count: totalCount,
|
|
18
|
-
limit,
|
|
19
|
-
offset,
|
|
20
|
-
has_more: offset + limit < totalCount,
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Error helpers
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
export function errorResponse(message, nextSteps) {
|
|
27
|
-
return { status: "error", message, next_steps: nextSteps };
|
|
28
|
-
}
|
|
29
|
-
export function notFoundError(resource, id) {
|
|
30
|
-
return errorResponse(`${resource} '${id}' not found`, [
|
|
31
|
-
`Verify the ${resource.toLowerCase()} ID is correct`,
|
|
32
|
-
`Use atoms_list_items or atoms_search to find valid IDs`,
|
|
33
|
-
]);
|
|
34
|
-
}
|
|
35
|
-
export function accessDeniedError(role) {
|
|
36
|
-
return errorResponse(`${role} role cannot modify project data`, [
|
|
37
|
-
"Contact your org admin to upgrade your role to editor",
|
|
38
|
-
"Use read-only tools (atoms_list_items, atoms_get_item, atoms_search) instead",
|
|
39
|
-
]);
|
|
40
|
-
}
|
|
41
|
-
export function validationError(message) {
|
|
42
|
-
return errorResponse(`Validation error: ${message}`, [
|
|
43
|
-
"Check the parameter types and constraints in the tool description",
|
|
44
|
-
"Ensure all required parameters are provided",
|
|
45
|
-
]);
|
|
46
|
-
}
|
|
47
|
-
export function authError() {
|
|
48
|
-
return errorResponse("Not authenticated. Run 'npx @atoms-tech/atoms-mcp login' first.", [
|
|
49
|
-
"Run: npx @atoms-tech/atoms-mcp login",
|
|
50
|
-
"Or set ATOMS_ACCESS_TOKEN environment variable",
|
|
51
|
-
]);
|
|
52
|
-
}
|
|
53
|
-
export function rateLimitError(retryAfterSeconds) {
|
|
54
|
-
return errorResponse("Rate limit exceeded", [
|
|
55
|
-
`Wait ${retryAfterSeconds} seconds before making more requests`,
|
|
56
|
-
"Reduce the frequency of tool calls",
|
|
57
|
-
]);
|
|
58
|
-
}
|
|
59
|
-
export function dbError(message) {
|
|
60
|
-
return errorResponse(`Database error: ${message}`, [
|
|
61
|
-
"This may be a temporary issue — try again",
|
|
62
|
-
"If the error persists, check that the project_id is valid",
|
|
63
|
-
]);
|
|
64
|
-
}
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
// Response formatting
|
|
67
|
-
// ---------------------------------------------------------------------------
|
|
68
|
-
/**
|
|
69
|
-
* Format a tool result as JSON string + structuredContent for the MCP SDK.
|
|
70
|
-
* Applies CHARACTER_LIMIT truncation if needed.
|
|
71
|
-
*/
|
|
72
|
-
export function formatToolResult(result) {
|
|
73
|
-
const json = JSON.stringify(result, null, 2);
|
|
74
|
-
if (json.length > CHARACTER_LIMIT) {
|
|
75
|
-
// If it's a success with array data, truncate the data
|
|
76
|
-
if (result.status === "success" && Array.isArray(result.data)) {
|
|
77
|
-
const halfLength = Math.max(1, Math.floor(result.data.length / 2));
|
|
78
|
-
const truncated = {
|
|
79
|
-
...result,
|
|
80
|
-
data: result.data.slice(0, halfLength),
|
|
81
|
-
meta: {
|
|
82
|
-
...result.meta,
|
|
83
|
-
truncated: true,
|
|
84
|
-
truncation_message: `Response truncated from ${result.data.length} to ${halfLength} items. ` +
|
|
85
|
-
`Use 'offset' parameter or add filters to see more results.`,
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
return {
|
|
89
|
-
content: [{ type: "text", text: JSON.stringify(truncated, null, 2) }],
|
|
90
|
-
structuredContent: truncated,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
return {
|
|
95
|
-
content: [{ type: "text", text: json }],
|
|
96
|
-
structuredContent: result,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Format an error result for the MCP SDK.
|
|
101
|
-
*/
|
|
102
|
-
export function formatErrorResult(error) {
|
|
103
|
-
return {
|
|
104
|
-
content: [{ type: "text", text: JSON.stringify(error, null, 2) }],
|
|
105
|
-
structuredContent: error,
|
|
106
|
-
isError: true,
|
|
107
|
-
};
|
|
108
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_bulk_import — Bulk create multiple items in a single tool call.
|
|
3
|
-
* Essential for AI agents that generate 20+ requirements at once.
|
|
4
|
-
*
|
|
5
|
-
* Checks write access once, generates sequential IDs, batch inserts,
|
|
6
|
-
* and reports per-item errors without aborting the entire batch.
|
|
7
|
-
*/
|
|
8
|
-
interface BulkImportItem {
|
|
9
|
-
type: "requirement" | "test-case" | "note";
|
|
10
|
-
title: string;
|
|
11
|
-
body?: string;
|
|
12
|
-
summary?: string;
|
|
13
|
-
domains?: string[];
|
|
14
|
-
level?: string;
|
|
15
|
-
parent_id?: string;
|
|
16
|
-
}
|
|
17
|
-
interface CreatedItem {
|
|
18
|
-
id: string;
|
|
19
|
-
title: string;
|
|
20
|
-
type: string;
|
|
21
|
-
}
|
|
22
|
-
interface BulkImportError {
|
|
23
|
-
index: number;
|
|
24
|
-
title: string;
|
|
25
|
-
error: string;
|
|
26
|
-
}
|
|
27
|
-
export declare function bulkImportHandler(params: {
|
|
28
|
-
project_id: string;
|
|
29
|
-
items: BulkImportItem[];
|
|
30
|
-
}): Promise<{
|
|
31
|
-
content: {
|
|
32
|
-
type: "text";
|
|
33
|
-
text: string;
|
|
34
|
-
}[];
|
|
35
|
-
structuredContent: {
|
|
36
|
-
data: any[];
|
|
37
|
-
meta: {
|
|
38
|
-
truncated: boolean;
|
|
39
|
-
truncation_message: string;
|
|
40
|
-
total_count?: number | undefined;
|
|
41
|
-
limit?: number | undefined;
|
|
42
|
-
offset?: number | undefined;
|
|
43
|
-
has_more?: boolean | undefined;
|
|
44
|
-
};
|
|
45
|
-
status: "success";
|
|
46
|
-
};
|
|
47
|
-
} | {
|
|
48
|
-
content: {
|
|
49
|
-
type: "text";
|
|
50
|
-
text: string;
|
|
51
|
-
}[];
|
|
52
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
|
|
53
|
-
created: number;
|
|
54
|
-
items: never[];
|
|
55
|
-
errors: BulkImportError[];
|
|
56
|
-
}>;
|
|
57
|
-
} | {
|
|
58
|
-
content: {
|
|
59
|
-
type: "text";
|
|
60
|
-
text: string;
|
|
61
|
-
}[];
|
|
62
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
|
|
63
|
-
created: number;
|
|
64
|
-
items: CreatedItem[];
|
|
65
|
-
errors: BulkImportError[];
|
|
66
|
-
project_id: string;
|
|
67
|
-
}>;
|
|
68
|
-
}>;
|
|
69
|
-
export {};
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_bulk_import — Bulk create multiple items in a single tool call.
|
|
3
|
-
* Essential for AI agents that generate 20+ requirements at once.
|
|
4
|
-
*
|
|
5
|
-
* Checks write access once, generates sequential IDs, batch inserts,
|
|
6
|
-
* and reports per-item errors without aborting the entire batch.
|
|
7
|
-
*/
|
|
8
|
-
import { getClient, getUserId, requireWriteAccess } from "../db/client.js";
|
|
9
|
-
import { generateItemId } from "../db/queries.js";
|
|
10
|
-
import { getMcpSessionId } from "../server.js";
|
|
11
|
-
import { formatToolResult, formatErrorResult, success, accessDeniedError, validationError, dbError, authError, } from "./_base.js";
|
|
12
|
-
export async function bulkImportHandler(params) {
|
|
13
|
-
try {
|
|
14
|
-
// Validate item count
|
|
15
|
-
if (!params.items || params.items.length === 0) {
|
|
16
|
-
return formatErrorResult(validationError("items array must contain at least 1 item"));
|
|
17
|
-
}
|
|
18
|
-
if (params.items.length > 100) {
|
|
19
|
-
return formatErrorResult(validationError(`Maximum 100 items per call (received ${params.items.length}). Split into multiple calls.`));
|
|
20
|
-
}
|
|
21
|
-
const client = await getClient();
|
|
22
|
-
const userId = getUserId();
|
|
23
|
-
// Check write access once for the entire batch
|
|
24
|
-
try {
|
|
25
|
-
await requireWriteAccess(client, params.project_id);
|
|
26
|
-
}
|
|
27
|
-
catch (err) {
|
|
28
|
-
if (err instanceof Error && err.message === "VIEWER_ROLE") {
|
|
29
|
-
return formatErrorResult(accessDeniedError("Viewer"));
|
|
30
|
-
}
|
|
31
|
-
throw err;
|
|
32
|
-
}
|
|
33
|
-
const now = new Date().toISOString();
|
|
34
|
-
const sessionId = getMcpSessionId();
|
|
35
|
-
const created = [];
|
|
36
|
-
const errors = [];
|
|
37
|
-
// Generate all IDs sequentially (each call increments the counter)
|
|
38
|
-
const itemsWithIds = [];
|
|
39
|
-
for (let i = 0; i < params.items.length; i++) {
|
|
40
|
-
const input = params.items[i];
|
|
41
|
-
try {
|
|
42
|
-
const itemId = await generateItemId(client, params.project_id, input.type);
|
|
43
|
-
itemsWithIds.push({ itemId, input, index: i });
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
errors.push({
|
|
47
|
-
index: i,
|
|
48
|
-
title: input.title,
|
|
49
|
-
error: `ID generation failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
if (itemsWithIds.length === 0) {
|
|
54
|
-
return formatToolResult(success({
|
|
55
|
-
created: 0,
|
|
56
|
-
items: [],
|
|
57
|
-
errors,
|
|
58
|
-
}));
|
|
59
|
-
}
|
|
60
|
-
// Build insert rows and WorkItem data blobs
|
|
61
|
-
const insertRows = [];
|
|
62
|
-
const workItemMap = new Map();
|
|
63
|
-
for (const { itemId, input } of itemsWithIds) {
|
|
64
|
-
const workItem = {
|
|
65
|
-
id: itemId,
|
|
66
|
-
type: input.type,
|
|
67
|
-
title: input.title,
|
|
68
|
-
summary: input.summary,
|
|
69
|
-
body: input.body,
|
|
70
|
-
tags: {
|
|
71
|
-
domains: input.domains ?? [],
|
|
72
|
-
level: input.level,
|
|
73
|
-
},
|
|
74
|
-
ownership: {
|
|
75
|
-
primary: null,
|
|
76
|
-
additional: [],
|
|
77
|
-
},
|
|
78
|
-
relationships: {
|
|
79
|
-
parents: input.parent_id ? [input.parent_id] : [],
|
|
80
|
-
children: [],
|
|
81
|
-
related: [],
|
|
82
|
-
},
|
|
83
|
-
links: [],
|
|
84
|
-
metadata: {
|
|
85
|
-
created_at: now,
|
|
86
|
-
created_by: userId,
|
|
87
|
-
updated_at: now,
|
|
88
|
-
updated_by: userId,
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
workItemMap.set(itemId, workItem);
|
|
92
|
-
insertRows.push({
|
|
93
|
-
id: itemId,
|
|
94
|
-
project_id: params.project_id,
|
|
95
|
-
type: input.type,
|
|
96
|
-
title: input.title,
|
|
97
|
-
data: workItem,
|
|
98
|
-
created_by: userId,
|
|
99
|
-
updated_by: userId,
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
// Batch insert all items
|
|
103
|
-
const { error: insertErr } = await client
|
|
104
|
-
.from("items")
|
|
105
|
-
.insert(insertRows);
|
|
106
|
-
if (insertErr) {
|
|
107
|
-
// If batch insert fails, fall back to individual inserts
|
|
108
|
-
for (const { itemId, input, index } of itemsWithIds) {
|
|
109
|
-
const row = insertRows.find((r) => r.id === itemId);
|
|
110
|
-
if (!row)
|
|
111
|
-
continue;
|
|
112
|
-
const { error: singleErr } = await client
|
|
113
|
-
.from("items")
|
|
114
|
-
.insert(row);
|
|
115
|
-
if (singleErr) {
|
|
116
|
-
errors.push({
|
|
117
|
-
index,
|
|
118
|
-
title: input.title,
|
|
119
|
-
error: singleErr.message,
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
created.push({ id: itemId, title: input.title, type: input.type });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
else {
|
|
128
|
-
// Batch succeeded — all items created
|
|
129
|
-
for (const { itemId, input } of itemsWithIds) {
|
|
130
|
-
created.push({ id: itemId, title: input.title, type: input.type });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// Create relationships for items with parent_id (fire-and-forget)
|
|
134
|
-
const relRows = itemsWithIds
|
|
135
|
-
.filter(({ input }) => input.parent_id)
|
|
136
|
-
.filter(({ itemId }) => created.some((c) => c.id === itemId))
|
|
137
|
-
.map(({ itemId, input }) => ({
|
|
138
|
-
from_id: itemId,
|
|
139
|
-
to_id: input.parent_id,
|
|
140
|
-
type: "parent",
|
|
141
|
-
project_id: params.project_id,
|
|
142
|
-
}));
|
|
143
|
-
if (relRows.length > 0) {
|
|
144
|
-
await client
|
|
145
|
-
.from("item_relationships")
|
|
146
|
-
.insert(relRows)
|
|
147
|
-
.then(({ error }) => {
|
|
148
|
-
if (error) {
|
|
149
|
-
process.stderr.write(`[atoms-mcp] Warning: bulk relationship sync failed: ${error.message}\n`);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
// Audit: log one change_history entry per created item (fire-and-forget)
|
|
154
|
-
const historyRows = created.map((item) => ({
|
|
155
|
-
item_id: item.id,
|
|
156
|
-
project_id: params.project_id,
|
|
157
|
-
changed_by: userId,
|
|
158
|
-
event_type: "created",
|
|
159
|
-
old_data: null,
|
|
160
|
-
new_data: workItemMap.get(item.id) ?? null,
|
|
161
|
-
actor: "mcp_claude",
|
|
162
|
-
session_id: sessionId,
|
|
163
|
-
}));
|
|
164
|
-
if (historyRows.length > 0) {
|
|
165
|
-
await client
|
|
166
|
-
.from("change_history")
|
|
167
|
-
.insert(historyRows)
|
|
168
|
-
.then(({ error }) => {
|
|
169
|
-
if (error) {
|
|
170
|
-
process.stderr.write(`[atoms-mcp] Warning: bulk change_history log failed: ${error.message}\n`);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
return formatToolResult(success({
|
|
175
|
-
created: created.length,
|
|
176
|
-
items: created,
|
|
177
|
-
errors: errors.length > 0 ? errors : [],
|
|
178
|
-
project_id: params.project_id,
|
|
179
|
-
}));
|
|
180
|
-
}
|
|
181
|
-
catch (err) {
|
|
182
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
183
|
-
return formatErrorResult(authError());
|
|
184
|
-
}
|
|
185
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
186
|
-
}
|
|
187
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_create_item — Create a new requirement, test case, or note.
|
|
3
|
-
* Requires editor or admin role. Logs with AI actor attribution.
|
|
4
|
-
*/
|
|
5
|
-
export declare function createItemHandler(params: {
|
|
6
|
-
project_id: string;
|
|
7
|
-
type: "requirement" | "test-case" | "note";
|
|
8
|
-
title: string;
|
|
9
|
-
body?: string;
|
|
10
|
-
summary?: string;
|
|
11
|
-
domains?: string[];
|
|
12
|
-
level?: string;
|
|
13
|
-
parent_ids?: string[];
|
|
14
|
-
}): Promise<{
|
|
15
|
-
content: {
|
|
16
|
-
type: "text";
|
|
17
|
-
text: string;
|
|
18
|
-
}[];
|
|
19
|
-
structuredContent: {
|
|
20
|
-
data: any[];
|
|
21
|
-
meta: {
|
|
22
|
-
truncated: boolean;
|
|
23
|
-
truncation_message: string;
|
|
24
|
-
total_count?: number | undefined;
|
|
25
|
-
limit?: number | undefined;
|
|
26
|
-
offset?: number | undefined;
|
|
27
|
-
has_more?: boolean | undefined;
|
|
28
|
-
};
|
|
29
|
-
status: "success";
|
|
30
|
-
};
|
|
31
|
-
} | {
|
|
32
|
-
content: {
|
|
33
|
-
type: "text";
|
|
34
|
-
text: string;
|
|
35
|
-
}[];
|
|
36
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
|
|
37
|
-
id: string;
|
|
38
|
-
type: "requirement" | "test-case" | "note";
|
|
39
|
-
title: string;
|
|
40
|
-
project_id: string;
|
|
41
|
-
}>;
|
|
42
|
-
}>;
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_create_item — Create a new requirement, test case, or note.
|
|
3
|
-
* Requires editor or admin role. Logs with AI actor attribution.
|
|
4
|
-
*/
|
|
5
|
-
import { getClient, getUserId, requireWriteAccess } from "../db/client.js";
|
|
6
|
-
import { generateItemId } from "../db/queries.js";
|
|
7
|
-
import { getMcpSessionId } from "../server.js";
|
|
8
|
-
import { formatToolResult, formatErrorResult, success, accessDeniedError, dbError, authError, } from "./_base.js";
|
|
9
|
-
export async function createItemHandler(params) {
|
|
10
|
-
try {
|
|
11
|
-
const client = await getClient();
|
|
12
|
-
const userId = getUserId();
|
|
13
|
-
// Check write access
|
|
14
|
-
try {
|
|
15
|
-
await requireWriteAccess(client, params.project_id);
|
|
16
|
-
}
|
|
17
|
-
catch (err) {
|
|
18
|
-
if (err instanceof Error && err.message === "VIEWER_ROLE") {
|
|
19
|
-
return formatErrorResult(accessDeniedError("Viewer"));
|
|
20
|
-
}
|
|
21
|
-
throw err;
|
|
22
|
-
}
|
|
23
|
-
// Generate next ID (uses SECURITY DEFINER function to avoid collisions)
|
|
24
|
-
const itemId = await generateItemId(client, params.project_id, params.type);
|
|
25
|
-
// Build WorkItem data blob
|
|
26
|
-
const workItem = {
|
|
27
|
-
id: itemId,
|
|
28
|
-
type: params.type,
|
|
29
|
-
title: params.title,
|
|
30
|
-
summary: params.summary,
|
|
31
|
-
body: params.body,
|
|
32
|
-
tags: {
|
|
33
|
-
domains: params.domains ?? [],
|
|
34
|
-
level: params.level,
|
|
35
|
-
},
|
|
36
|
-
ownership: {
|
|
37
|
-
primary: null,
|
|
38
|
-
additional: [],
|
|
39
|
-
},
|
|
40
|
-
relationships: {
|
|
41
|
-
parents: params.parent_ids ?? [],
|
|
42
|
-
children: [],
|
|
43
|
-
related: [],
|
|
44
|
-
},
|
|
45
|
-
links: [],
|
|
46
|
-
metadata: {
|
|
47
|
-
created_at: new Date().toISOString(),
|
|
48
|
-
created_by: userId,
|
|
49
|
-
updated_at: new Date().toISOString(),
|
|
50
|
-
updated_by: userId,
|
|
51
|
-
},
|
|
52
|
-
};
|
|
53
|
-
// Insert to items table
|
|
54
|
-
const { error: insertErr } = await client
|
|
55
|
-
.from("items")
|
|
56
|
-
.insert({
|
|
57
|
-
id: itemId,
|
|
58
|
-
project_id: params.project_id,
|
|
59
|
-
type: params.type,
|
|
60
|
-
title: params.title,
|
|
61
|
-
data: workItem,
|
|
62
|
-
created_by: userId,
|
|
63
|
-
updated_by: userId,
|
|
64
|
-
})
|
|
65
|
-
.select()
|
|
66
|
-
.single();
|
|
67
|
-
if (insertErr)
|
|
68
|
-
throw new Error(insertErr.message);
|
|
69
|
-
// Dual-write: sync relationships to shadow table
|
|
70
|
-
if (params.parent_ids && params.parent_ids.length > 0) {
|
|
71
|
-
const relRows = params.parent_ids.map((parentId) => ({
|
|
72
|
-
from_id: itemId,
|
|
73
|
-
to_id: parentId,
|
|
74
|
-
type: "parent",
|
|
75
|
-
project_id: params.project_id,
|
|
76
|
-
}));
|
|
77
|
-
await client
|
|
78
|
-
.from("item_relationships")
|
|
79
|
-
.insert(relRows)
|
|
80
|
-
.then(({ error }) => {
|
|
81
|
-
if (error) {
|
|
82
|
-
process.stderr.write(`[atoms-mcp] Warning: relationship sync failed: ${error.message}\n`);
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
// Audit: log to change_history with MCP actor
|
|
87
|
-
await client
|
|
88
|
-
.from("change_history")
|
|
89
|
-
.insert({
|
|
90
|
-
item_id: itemId,
|
|
91
|
-
project_id: params.project_id,
|
|
92
|
-
changed_by: userId,
|
|
93
|
-
event_type: "created",
|
|
94
|
-
old_data: null,
|
|
95
|
-
new_data: workItem,
|
|
96
|
-
actor: "mcp_claude",
|
|
97
|
-
session_id: getMcpSessionId(),
|
|
98
|
-
})
|
|
99
|
-
.then(({ error }) => {
|
|
100
|
-
if (error) {
|
|
101
|
-
process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${error.message}\n`);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
return formatToolResult(success({
|
|
105
|
-
id: itemId,
|
|
106
|
-
type: params.type,
|
|
107
|
-
title: params.title,
|
|
108
|
-
project_id: params.project_id,
|
|
109
|
-
}));
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
113
|
-
return formatErrorResult(authError());
|
|
114
|
-
}
|
|
115
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
116
|
-
}
|
|
117
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_delete_item — Soft-delete an item (sets deleted_at, preserves for audit).
|
|
3
|
-
* Requires editor or admin role.
|
|
4
|
-
*/
|
|
5
|
-
export declare function deleteItemHandler(params: {
|
|
6
|
-
project_id: string;
|
|
7
|
-
item_id: string;
|
|
8
|
-
}): Promise<{
|
|
9
|
-
content: {
|
|
10
|
-
type: "text";
|
|
11
|
-
text: string;
|
|
12
|
-
}[];
|
|
13
|
-
structuredContent: {
|
|
14
|
-
data: any[];
|
|
15
|
-
meta: {
|
|
16
|
-
truncated: boolean;
|
|
17
|
-
truncation_message: string;
|
|
18
|
-
total_count?: number | undefined;
|
|
19
|
-
limit?: number | undefined;
|
|
20
|
-
offset?: number | undefined;
|
|
21
|
-
has_more?: boolean | undefined;
|
|
22
|
-
};
|
|
23
|
-
status: "success";
|
|
24
|
-
};
|
|
25
|
-
} | {
|
|
26
|
-
content: {
|
|
27
|
-
type: "text";
|
|
28
|
-
text: string;
|
|
29
|
-
}[];
|
|
30
|
-
structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
|
|
31
|
-
id: string;
|
|
32
|
-
title: string;
|
|
33
|
-
type: string;
|
|
34
|
-
deleted: boolean;
|
|
35
|
-
message: string;
|
|
36
|
-
}>;
|
|
37
|
-
}>;
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* atoms_delete_item — Soft-delete an item (sets deleted_at, preserves for audit).
|
|
3
|
-
* Requires editor or admin role.
|
|
4
|
-
*/
|
|
5
|
-
import { getClient, getUserId, requireWriteAccess } from "../db/client.js";
|
|
6
|
-
import { getItemById } from "../db/queries.js";
|
|
7
|
-
import { getMcpSessionId } from "../server.js";
|
|
8
|
-
import { formatToolResult, formatErrorResult, success, notFoundError, accessDeniedError, dbError, authError, } from "./_base.js";
|
|
9
|
-
export async function deleteItemHandler(params) {
|
|
10
|
-
try {
|
|
11
|
-
const client = await getClient();
|
|
12
|
-
const userId = getUserId();
|
|
13
|
-
// Check write access
|
|
14
|
-
try {
|
|
15
|
-
await requireWriteAccess(client, params.project_id);
|
|
16
|
-
}
|
|
17
|
-
catch (err) {
|
|
18
|
-
if (err instanceof Error && err.message === "VIEWER_ROLE") {
|
|
19
|
-
return formatErrorResult(accessDeniedError("Viewer"));
|
|
20
|
-
}
|
|
21
|
-
throw err;
|
|
22
|
-
}
|
|
23
|
-
// Get existing item
|
|
24
|
-
const existing = await getItemById(client, params.project_id, params.item_id);
|
|
25
|
-
if (!existing) {
|
|
26
|
-
return formatErrorResult(notFoundError("Item", params.item_id));
|
|
27
|
-
}
|
|
28
|
-
// Soft delete (set deleted_at, never remove the row)
|
|
29
|
-
const { error: deleteErr } = await client
|
|
30
|
-
.from("items")
|
|
31
|
-
.update({ deleted_at: new Date().toISOString() })
|
|
32
|
-
.eq("id", params.item_id)
|
|
33
|
-
.eq("project_id", params.project_id);
|
|
34
|
-
if (deleteErr)
|
|
35
|
-
throw new Error(deleteErr.message);
|
|
36
|
-
// Audit: log deletion to change_history
|
|
37
|
-
await client
|
|
38
|
-
.from("change_history")
|
|
39
|
-
.insert({
|
|
40
|
-
item_id: params.item_id,
|
|
41
|
-
project_id: params.project_id,
|
|
42
|
-
changed_by: userId,
|
|
43
|
-
event_type: "deleted",
|
|
44
|
-
old_data: existing.data,
|
|
45
|
-
new_data: null,
|
|
46
|
-
actor: "mcp_claude",
|
|
47
|
-
session_id: getMcpSessionId(),
|
|
48
|
-
})
|
|
49
|
-
.then(({ error }) => {
|
|
50
|
-
if (error) {
|
|
51
|
-
process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${error.message}\n`);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
return formatToolResult(success({
|
|
55
|
-
id: params.item_id,
|
|
56
|
-
title: existing.title,
|
|
57
|
-
type: existing.type,
|
|
58
|
-
deleted: true,
|
|
59
|
-
message: `Item ${params.item_id} soft-deleted. It can still be found in audit logs.`,
|
|
60
|
-
}));
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
if (err instanceof Error && err.message.includes("Not authenticated")) {
|
|
64
|
-
return formatErrorResult(authError());
|
|
65
|
-
}
|
|
66
|
-
return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
|
|
67
|
-
}
|
|
68
|
-
}
|