@atoms-tech/atoms-mcp 0.3.0 → 0.3.2

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 (65) hide show
  1. package/README.md +1 -1
  2. package/dist/index.cjs +2 -0
  3. package/dist/index.js +1 -90
  4. package/package.json +12 -3
  5. package/dist/apps/register.d.ts +0 -36
  6. package/dist/apps/register.js +0 -65
  7. package/dist/auth/login.d.ts +0 -24
  8. package/dist/auth/login.js +0 -413
  9. package/dist/auth/refresh.d.ts +0 -16
  10. package/dist/auth/refresh.js +0 -81
  11. package/dist/auth/token-store.d.ts +0 -33
  12. package/dist/auth/token-store.js +0 -60
  13. package/dist/config.d.ts +0 -18
  14. package/dist/config.js +0 -18
  15. package/dist/db/client.d.ts +0 -29
  16. package/dist/db/client.js +0 -109
  17. package/dist/db/graph.d.ts +0 -7
  18. package/dist/db/graph.js +0 -7
  19. package/dist/db/queries.d.ts +0 -76
  20. package/dist/db/queries.js +0 -209
  21. package/dist/index.d.ts +0 -11
  22. package/dist/middleware/audit.d.ts +0 -25
  23. package/dist/middleware/audit.js +0 -43
  24. package/dist/middleware/rate-limiter.d.ts +0 -20
  25. package/dist/middleware/rate-limiter.js +0 -42
  26. package/dist/middleware/validator.d.ts +0 -21
  27. package/dist/middleware/validator.js +0 -90
  28. package/dist/server.d.ts +0 -14
  29. package/dist/server.js +0 -679
  30. package/dist/tools/_base.d.ts +0 -57
  31. package/dist/tools/_base.js +0 -108
  32. package/dist/tools/bulk-import.d.ts +0 -69
  33. package/dist/tools/bulk-import.js +0 -187
  34. package/dist/tools/create-item.d.ts +0 -42
  35. package/dist/tools/create-item.js +0 -117
  36. package/dist/tools/delete-item.d.ts +0 -37
  37. package/dist/tools/delete-item.js +0 -68
  38. package/dist/tools/export-mermaid.d.ts +0 -35
  39. package/dist/tools/export-mermaid.js +0 -124
  40. package/dist/tools/get-coverage.d.ts +0 -33
  41. package/dist/tools/get-coverage.js +0 -35
  42. package/dist/tools/get-history.d.ts +0 -33
  43. package/dist/tools/get-history.js +0 -52
  44. package/dist/tools/get-item.d.ts +0 -61
  45. package/dist/tools/get-item.js +0 -92
  46. package/dist/tools/link-items.d.ts +0 -40
  47. package/dist/tools/link-items.js +0 -149
  48. package/dist/tools/list-items.d.ts +0 -36
  49. package/dist/tools/list-items.js +0 -35
  50. package/dist/tools/list-projects.d.ts +0 -37
  51. package/dist/tools/list-projects.js +0 -27
  52. package/dist/tools/project-summary.d.ts +0 -63
  53. package/dist/tools/project-summary.js +0 -169
  54. package/dist/tools/record-test-result.d.ts +0 -40
  55. package/dist/tools/record-test-result.js +0 -79
  56. package/dist/tools/search.d.ts +0 -33
  57. package/dist/tools/search.js +0 -27
  58. package/dist/tools/trace.d.ts +0 -52
  59. package/dist/tools/trace.js +0 -165
  60. package/dist/tools/update-item.d.ts +0 -42
  61. package/dist/tools/update-item.js +0 -97
  62. package/dist/types/responses.d.ts +0 -57
  63. package/dist/types/responses.js +0 -5
  64. package/dist/types/work-item.d.ts +0 -68
  65. package/dist/types/work-item.js +0 -7
@@ -1,35 +0,0 @@
1
- /**
2
- * atoms_export_mermaid — Generate Mermaid diagram of requirement/test hierarchy.
3
- * Output is paste-ready for markdown documentation.
4
- */
5
- export declare function exportMermaidHandler(params: {
6
- project_id: string;
7
- root_item_id?: string;
8
- depth: number;
9
- include_tests: boolean;
10
- }): Promise<{
11
- content: {
12
- type: "text";
13
- text: string;
14
- }[];
15
- structuredContent: {
16
- data: any[];
17
- meta: {
18
- truncated: boolean;
19
- truncation_message: string;
20
- total_count?: number | undefined;
21
- limit?: number | undefined;
22
- offset?: number | undefined;
23
- has_more?: boolean | undefined;
24
- };
25
- status: "success";
26
- };
27
- } | {
28
- content: {
29
- type: "text";
30
- text: string;
31
- }[];
32
- structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
33
- mermaid: string;
34
- }>;
35
- }>;
@@ -1,124 +0,0 @@
1
- /**
2
- * atoms_export_mermaid — Generate Mermaid diagram of requirement/test hierarchy.
3
- * Output is paste-ready for markdown documentation.
4
- */
5
- import { getClient } from "../db/client.js";
6
- import { formatToolResult, formatErrorResult, success, dbError, authError, } from "./_base.js";
7
- export async function exportMermaidHandler(params) {
8
- try {
9
- const client = await getClient();
10
- // Get all items in the project
11
- const { data: items, error: itemsErr } = await client
12
- .from("items")
13
- .select("id, title, type, data")
14
- .eq("project_id", params.project_id)
15
- .is("deleted_at", null);
16
- if (itemsErr)
17
- throw new Error(itemsErr.message);
18
- if (!items || items.length === 0) {
19
- return formatToolResult(success({ mermaid: "graph TD\n empty[No items in project]" }));
20
- }
21
- // Get all relationships
22
- const { data: rels, error: relsErr } = await client
23
- .from("item_relationships")
24
- .select("from_id, to_id, type")
25
- .eq("project_id", params.project_id);
26
- if (relsErr)
27
- throw new Error(relsErr.message);
28
- const itemMap = new Map();
29
- for (const item of items) {
30
- itemMap.set(item.id, item);
31
- }
32
- // Build Mermaid graph
33
- const lines = ["graph TD"];
34
- const visited = new Set();
35
- const edges = new Set();
36
- // Find root nodes (items with no parents)
37
- const childIds = new Set((rels ?? []).filter((r) => r.type === "child").map((r) => r.to_id));
38
- const parentIds = new Set((rels ?? []).filter((r) => r.type === "parent").map((r) => r.from_id));
39
- let roots;
40
- if (params.root_item_id) {
41
- roots = [params.root_item_id];
42
- }
43
- else {
44
- // Items that are not children of anything
45
- roots = items
46
- .filter((item) => !parentIds.has(item.id))
47
- .filter((item) => {
48
- if (!params.include_tests && item.type === "test-case")
49
- return false;
50
- return true;
51
- })
52
- .map((item) => item.id);
53
- }
54
- // BFS to build graph up to depth
55
- const queue = roots.map((id) => ({
56
- id,
57
- depth: 0,
58
- }));
59
- while (queue.length > 0) {
60
- const { id, depth } = queue.shift();
61
- if (visited.has(id) || depth > params.depth)
62
- continue;
63
- visited.add(id);
64
- const item = itemMap.get(id);
65
- if (!item)
66
- continue;
67
- // Skip test cases if not included
68
- if (!params.include_tests && item.type === "test-case")
69
- continue;
70
- // Add node
71
- const label = sanitizeLabel(item.title);
72
- const shape = item.type === "requirement"
73
- ? `["${label}"]`
74
- : item.type === "test-case"
75
- ? `(["${label}"])`
76
- : `("${label}")`;
77
- lines.push(` ${id}${shape}`);
78
- // Add edges from relationships
79
- for (const rel of rels ?? []) {
80
- if (rel.from_id !== id)
81
- continue;
82
- const targetItem = itemMap.get(rel.to_id);
83
- if (!targetItem)
84
- continue;
85
- if (!params.include_tests && targetItem.type === "test-case")
86
- continue;
87
- const edgeKey = `${rel.from_id}-${rel.type}-${rel.to_id}`;
88
- if (edges.has(edgeKey))
89
- continue;
90
- edges.add(edgeKey);
91
- if (rel.type === "child") {
92
- lines.push(` ${id} --> ${rel.to_id}`);
93
- }
94
- else if (rel.type === "verified_by") {
95
- lines.push(` ${id} -.->|verified_by| ${rel.to_id}`);
96
- }
97
- else if (rel.type === "related") {
98
- lines.push(` ${id} -.- ${rel.to_id}`);
99
- }
100
- if (!visited.has(rel.to_id) && depth + 1 <= params.depth) {
101
- queue.push({ id: rel.to_id, depth: depth + 1 });
102
- }
103
- }
104
- }
105
- const mermaid = lines.join("\n");
106
- return formatToolResult(success({
107
- mermaid,
108
- node_count: visited.size,
109
- edge_count: edges.size,
110
- }));
111
- }
112
- catch (err) {
113
- if (err instanceof Error && err.message.includes("Not authenticated")) {
114
- return formatErrorResult(authError());
115
- }
116
- return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
117
- }
118
- }
119
- function sanitizeLabel(title) {
120
- return title
121
- .replace(/"/g, "'")
122
- .replace(/[[\]{}()]/g, "")
123
- .substring(0, 50);
124
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * atoms_get_coverage — Find requirements without linked test cases.
3
- * Essential for compliance reporting in safety-critical industries.
4
- */
5
- import type { CoverageReport } from "../types/responses.js";
6
- export declare function getCoverageHandler(params: {
7
- project_id: string;
8
- domain?: string;
9
- level?: string;
10
- }): Promise<{
11
- content: {
12
- type: "text";
13
- text: string;
14
- }[];
15
- structuredContent: {
16
- data: any[];
17
- meta: {
18
- truncated: boolean;
19
- truncation_message: string;
20
- total_count?: number | undefined;
21
- limit?: number | undefined;
22
- offset?: number | undefined;
23
- has_more?: boolean | undefined;
24
- };
25
- status: "success";
26
- };
27
- } | {
28
- content: {
29
- type: "text";
30
- text: string;
31
- }[];
32
- structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<CoverageReport>;
33
- }>;
@@ -1,35 +0,0 @@
1
- /**
2
- * atoms_get_coverage — Find requirements without linked test cases.
3
- * Essential for compliance reporting in safety-critical industries.
4
- */
5
- import { getClient } from "../db/client.js";
6
- import { getCoverageReport } from "../db/queries.js";
7
- import { formatToolResult, formatErrorResult, success, dbError, authError, } from "./_base.js";
8
- export async function getCoverageHandler(params) {
9
- try {
10
- const client = await getClient();
11
- const { covered, uncovered, total } = await getCoverageReport(client, params.project_id, { domain: params.domain, level: params.level });
12
- const uncoveredItems = uncovered.map((item) => ({
13
- id: item.id,
14
- title: item.title,
15
- type: item.type,
16
- status: item.data?.status,
17
- domains: item.data?.tags?.domains ?? [],
18
- level: item.data?.tags?.level,
19
- }));
20
- const report = {
21
- covered: covered.length,
22
- uncovered: uncovered.length,
23
- total,
24
- coverage_percent: total > 0 ? Math.round((covered.length / total) * 1000) / 10 : 100,
25
- uncovered_items: uncoveredItems,
26
- };
27
- return formatToolResult(success(report));
28
- }
29
- catch (err) {
30
- if (err instanceof Error && err.message.includes("Not authenticated")) {
31
- return formatErrorResult(authError());
32
- }
33
- return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
34
- }
35
- }
@@ -1,33 +0,0 @@
1
- /**
2
- * atoms_get_history — Audit trail for an item.
3
- * Shows who changed what, when, and whether it was human or AI.
4
- */
5
- import type { HistoryEntry } from "../types/responses.js";
6
- export declare function getHistoryHandler(params: {
7
- project_id: string;
8
- item_id: string;
9
- limit: number;
10
- }): Promise<{
11
- content: {
12
- type: "text";
13
- text: string;
14
- }[];
15
- structuredContent: {
16
- data: any[];
17
- meta: {
18
- truncated: boolean;
19
- truncation_message: string;
20
- total_count?: number | undefined;
21
- limit?: number | undefined;
22
- offset?: number | undefined;
23
- has_more?: boolean | undefined;
24
- };
25
- status: "success";
26
- };
27
- } | {
28
- content: {
29
- type: "text";
30
- text: string;
31
- }[];
32
- structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<HistoryEntry[]>;
33
- }>;
@@ -1,52 +0,0 @@
1
- /**
2
- * atoms_get_history — Audit trail for an item.
3
- * Shows who changed what, when, and whether it was human or AI.
4
- */
5
- import { getClient } from "../db/client.js";
6
- import { getChangeHistory } from "../db/queries.js";
7
- import { formatToolResult, formatErrorResult, success, paginationMeta, dbError, authError, } from "./_base.js";
8
- export async function getHistoryHandler(params) {
9
- try {
10
- const client = await getClient();
11
- const history = await getChangeHistory(client, params.project_id, params.item_id, params.limit);
12
- const entries = history.map((entry) => {
13
- // Compute which fields changed by comparing old_data and new_data
14
- const fieldsChanged = [];
15
- const oldData = entry.old_data;
16
- const newData = entry.new_data;
17
- if (oldData && newData) {
18
- const allKeys = new Set([
19
- ...Object.keys(oldData),
20
- ...Object.keys(newData),
21
- ]);
22
- for (const key of allKeys) {
23
- if (JSON.stringify(oldData[key]) !== JSON.stringify(newData[key])) {
24
- fieldsChanged.push(key);
25
- }
26
- }
27
- }
28
- else if (newData) {
29
- fieldsChanged.push("(created)");
30
- }
31
- else if (oldData) {
32
- fieldsChanged.push("(deleted)");
33
- }
34
- return {
35
- id: entry.id,
36
- event_type: entry.event_type,
37
- changed_by: entry.changed_by ?? null,
38
- changed_at: entry.changed_at,
39
- actor: entry.actor ?? "user",
40
- session_id: entry.session_id ?? null,
41
- fields_changed: fieldsChanged,
42
- };
43
- });
44
- return formatToolResult(success(entries, paginationMeta(entries.length, params.limit, 0)));
45
- }
46
- catch (err) {
47
- if (err instanceof Error && err.message.includes("Not authenticated")) {
48
- return formatErrorResult(authError());
49
- }
50
- return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
51
- }
52
- }
@@ -1,61 +0,0 @@
1
- /**
2
- * atoms_get_item — Get full item details including relationships and test history.
3
- */
4
- export declare function getItemHandler(params: {
5
- project_id: string;
6
- item_id: string;
7
- }): Promise<{
8
- content: {
9
- type: "text";
10
- text: string;
11
- }[];
12
- structuredContent: {
13
- data: any[];
14
- meta: {
15
- truncated: boolean;
16
- truncation_message: string;
17
- total_count?: number | undefined;
18
- limit?: number | undefined;
19
- offset?: number | undefined;
20
- has_more?: boolean | undefined;
21
- };
22
- status: "success";
23
- };
24
- } | {
25
- content: {
26
- type: "text";
27
- text: string;
28
- }[];
29
- structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
30
- metadata: {
31
- created_at: string;
32
- created_by: string | null;
33
- updated_at: string;
34
- updated_by: string | null;
35
- source_id: string | undefined;
36
- };
37
- latest_result?: string | undefined;
38
- test_results?: {
39
- result: string;
40
- run_by: string | null;
41
- run_at: string;
42
- note: string | null;
43
- }[] | undefined;
44
- id: string;
45
- type: string;
46
- title: string;
47
- summary: string | undefined;
48
- body: string | undefined;
49
- tags: import("../types/work-item.js").WorkItemTags;
50
- ownership: import("../types/work-item.js").WorkItemOwnership;
51
- relationships: {
52
- parents: string[];
53
- children: string[];
54
- related: string[];
55
- verified_by: string[];
56
- verifies: string[];
57
- };
58
- links: import("../types/work-item.js").WorkItemLink[];
59
- status: "passed" | "failed" | "blocked" | "not-run" | undefined;
60
- }>;
61
- }>;
@@ -1,92 +0,0 @@
1
- /**
2
- * atoms_get_item — Get full item details including relationships and test history.
3
- */
4
- import { getClient } from "../db/client.js";
5
- import { getItemById, getItemRelationships } from "../db/queries.js";
6
- import { formatToolResult, formatErrorResult, success, notFoundError, dbError, authError, } from "./_base.js";
7
- export async function getItemHandler(params) {
8
- try {
9
- const client = await getClient();
10
- const item = await getItemById(client, params.project_id, params.item_id);
11
- if (!item) {
12
- return formatErrorResult(notFoundError("Item", params.item_id));
13
- }
14
- // Enrich with relationship data from shadow table
15
- const relationships = await getItemRelationships(client, params.project_id, params.item_id);
16
- // Build structured relationship map
17
- const relMap = {
18
- parents: [],
19
- children: [],
20
- related: [],
21
- verified_by: [],
22
- verifies: [],
23
- };
24
- for (const rel of relationships) {
25
- if (rel.from_id === params.item_id) {
26
- // Outgoing relationship
27
- if (rel.type in relMap) {
28
- relMap[rel.type].push(rel.to_id);
29
- }
30
- }
31
- else {
32
- // Incoming relationship — invert the type
33
- const inverse = {
34
- parent: "children",
35
- child: "parents",
36
- verified_by: "verifies",
37
- verifies: "verified_by",
38
- related: "related",
39
- };
40
- const key = inverse[rel.type] ?? rel.type;
41
- if (key in relMap) {
42
- relMap[key].push(rel.from_id);
43
- }
44
- }
45
- }
46
- // For test cases, fetch test results from shadow table
47
- let testResults = [];
48
- if (item.type === "test-case") {
49
- const { data: results, error: resultsErr } = await client
50
- .from("test_results")
51
- .select("result, run_by, run_at, note")
52
- .eq("item_id", params.item_id)
53
- .eq("project_id", params.project_id)
54
- .order("run_at", { ascending: false })
55
- .limit(20);
56
- if (!resultsErr && results) {
57
- testResults = results;
58
- }
59
- }
60
- const data = {
61
- id: item.id,
62
- type: item.type,
63
- title: item.title,
64
- summary: item.data?.summary,
65
- body: item.data?.body,
66
- tags: item.data?.tags ?? { domains: [] },
67
- ownership: item.data?.ownership ?? { primary: null, additional: [] },
68
- relationships: relMap,
69
- links: item.data?.links ?? [],
70
- status: item.data?.status,
71
- // Test results from shadow table (for test cases)
72
- ...(item.type === "test-case" ? {
73
- latest_result: testResults[0]?.result ?? "not-run",
74
- test_results: testResults,
75
- } : {}),
76
- metadata: {
77
- created_at: item.created_at,
78
- created_by: item.created_by,
79
- updated_at: item.updated_at,
80
- updated_by: item.updated_by,
81
- source_id: item.data?.metadata?.source_id,
82
- },
83
- };
84
- return formatToolResult(success(data));
85
- }
86
- catch (err) {
87
- if (err instanceof Error && err.message.includes("Not authenticated")) {
88
- return formatErrorResult(authError());
89
- }
90
- return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
91
- }
92
- }
@@ -1,40 +0,0 @@
1
- /**
2
- * atoms_link_items — Add or remove relationships between items.
3
- * Updates both items' JSONB data and the shadow table.
4
- */
5
- export declare function linkItemsHandler(params: {
6
- project_id: string;
7
- from_id: string;
8
- to_id: string;
9
- type: "parent" | "child" | "related" | "verifies" | "verified_by";
10
- action: "add" | "remove";
11
- }): Promise<{
12
- content: {
13
- type: "text";
14
- text: string;
15
- }[];
16
- structuredContent: {
17
- data: any[];
18
- meta: {
19
- truncated: boolean;
20
- truncation_message: string;
21
- total_count?: number | undefined;
22
- limit?: number | undefined;
23
- offset?: number | undefined;
24
- has_more?: boolean | undefined;
25
- };
26
- status: "success";
27
- };
28
- } | {
29
- content: {
30
- type: "text";
31
- text: string;
32
- }[];
33
- structuredContent: import("../types/responses.js").ToolError | import("../types/responses.js").ToolSuccess<{
34
- action: "add" | "remove";
35
- type: "parent" | "verified_by" | "child" | "verifies" | "related";
36
- from_id: string;
37
- to_id: string;
38
- message: string;
39
- }>;
40
- }>;
@@ -1,149 +0,0 @@
1
- /**
2
- * atoms_link_items — Add or remove relationships between items.
3
- * Updates both items' JSONB data and the shadow table.
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, validationError, dbError, authError, } from "./_base.js";
9
- export async function linkItemsHandler(params) {
10
- try {
11
- const client = await getClient();
12
- const userId = getUserId();
13
- // Validate: no self-referencing
14
- if (params.from_id === params.to_id) {
15
- return formatErrorResult(validationError("Cannot create a relationship between an item and itself"));
16
- }
17
- // Check write access
18
- try {
19
- await requireWriteAccess(client, params.project_id);
20
- }
21
- catch (err) {
22
- if (err instanceof Error && err.message === "VIEWER_ROLE") {
23
- return formatErrorResult(accessDeniedError("Viewer"));
24
- }
25
- throw err;
26
- }
27
- // Verify both items exist
28
- const fromItem = await getItemById(client, params.project_id, params.from_id);
29
- if (!fromItem) {
30
- return formatErrorResult(notFoundError("Item", params.from_id));
31
- }
32
- const toItem = await getItemById(client, params.project_id, params.to_id);
33
- if (!toItem) {
34
- return formatErrorResult(notFoundError("Item", params.to_id));
35
- }
36
- if (params.action === "add") {
37
- // Check if relationship already exists
38
- const { data: existing } = await client
39
- .from("item_relationships")
40
- .select("from_id")
41
- .eq("from_id", params.from_id)
42
- .eq("to_id", params.to_id)
43
- .eq("type", params.type)
44
- .eq("project_id", params.project_id)
45
- .maybeSingle();
46
- if (!existing) {
47
- const { error: relErr } = await client
48
- .from("item_relationships")
49
- .insert({
50
- from_id: params.from_id,
51
- to_id: params.to_id,
52
- type: params.type,
53
- project_id: params.project_id,
54
- });
55
- if (relErr)
56
- throw new Error(relErr.message);
57
- }
58
- // Update JSONB data on the from item
59
- const fromData = { ...fromItem.data };
60
- const relKey = params.type === "verified_by" || params.type === "verifies"
61
- ? params.type
62
- : params.type + "s" === "parents" ? "parents"
63
- : params.type === "parent" ? "parents"
64
- : params.type === "child" ? "children"
65
- : "related";
66
- const relField = relKey;
67
- if (fromData.relationships) {
68
- const existing = fromData.relationships[relField] ?? [];
69
- if (!existing.includes(params.to_id)) {
70
- fromData.relationships[relField] = [
71
- ...existing,
72
- params.to_id,
73
- ];
74
- }
75
- }
76
- await client
77
- .from("items")
78
- .update({ data: fromData, updated_by: userId })
79
- .eq("id", params.from_id)
80
- .eq("project_id", params.project_id);
81
- }
82
- else {
83
- // Remove from shadow table
84
- const { error: delErr } = await client
85
- .from("item_relationships")
86
- .delete()
87
- .eq("from_id", params.from_id)
88
- .eq("to_id", params.to_id)
89
- .eq("type", params.type)
90
- .eq("project_id", params.project_id);
91
- if (delErr)
92
- throw new Error(delErr.message);
93
- // Update JSONB data on the from item
94
- const fromData = { ...fromItem.data };
95
- const relKey = params.type === "parent" ? "parents"
96
- : params.type === "child" ? "children"
97
- : params.type;
98
- const relField = relKey;
99
- if (fromData.relationships) {
100
- const existing = fromData.relationships[relField] ?? [];
101
- fromData.relationships[relField] =
102
- existing.filter((id) => id !== params.to_id);
103
- }
104
- await client
105
- .from("items")
106
- .update({ data: fromData, updated_by: userId })
107
- .eq("id", params.from_id)
108
- .eq("project_id", params.project_id);
109
- }
110
- // Audit log
111
- await client
112
- .from("change_history")
113
- .insert({
114
- item_id: params.from_id,
115
- project_id: params.project_id,
116
- changed_by: userId,
117
- event_type: "updated",
118
- old_data: null,
119
- new_data: {
120
- relationship_change: {
121
- action: params.action,
122
- type: params.type,
123
- from: params.from_id,
124
- to: params.to_id,
125
- },
126
- },
127
- actor: "mcp_claude",
128
- session_id: getMcpSessionId(),
129
- })
130
- .then(({ error }) => {
131
- if (error) {
132
- process.stderr.write(`[atoms-mcp] Warning: change_history log failed: ${error.message}\n`);
133
- }
134
- });
135
- return formatToolResult(success({
136
- action: params.action,
137
- type: params.type,
138
- from_id: params.from_id,
139
- to_id: params.to_id,
140
- message: `Relationship ${params.action === "add" ? "added" : "removed"}: ${params.from_id} --[${params.type}]--> ${params.to_id}`,
141
- }));
142
- }
143
- catch (err) {
144
- if (err instanceof Error && err.message.includes("Not authenticated")) {
145
- return formatErrorResult(authError());
146
- }
147
- return formatErrorResult(dbError(err instanceof Error ? err.message : String(err)));
148
- }
149
- }