@cplace/test-mcp-server 0.1.7 → 0.1.9

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/dist/index.js CHANGED
@@ -13,19 +13,19 @@ import { convertPagesToCsv } from "./csvUtils.js";
13
13
  dotenv.config();
14
14
  const config = {
15
15
  url: process.env.CPLACE_URL || '',
16
- apiToken: process.env.API_TOKEN || ''
16
+ apiToken: process.env.API_TOKEN || '',
17
+ registerSystemInfoTool: process.env.REGISTER_SYSTEM_INFO_TOOL === 'true' || false,
17
18
  };
18
19
  const client = new CplaceApiClient(config);
19
- const REGISTER_SYSTEM_INFO_TOOL = false;
20
20
  const server = new McpServer({
21
21
  name: "cplace-mcp-server",
22
22
  version: "1.0.0"
23
23
  });
24
- if (REGISTER_SYSTEM_INFO_TOOL) {
25
- server.registerTool("cplace_system_info", {
24
+ if (config.registerSystemInfoTool) {
25
+ server.registerTool("cplace_get_system_info", {
26
26
  description: "Get system information about the MCP server - including current working directory, CPLACE_URL setting, and debug state",
27
27
  inputSchema: {},
28
- annotations: { title: "System Information" }
28
+ annotations: { title: "Get System Information" }
29
29
  }, async () => {
30
30
  const systemInfo = {
31
31
  currentWorkingDirectory: process.cwd(),
@@ -66,12 +66,12 @@ server.registerTool("cplace_list_workspaces", {
66
66
  };
67
67
  }
68
68
  });
69
- server.registerTool("cplace_get_types_in_workspace", {
70
- description: "Get a list of all types available in a certain workspace",
69
+ server.registerTool("cplace_list_types", {
70
+ description: "List all types available in a workspace",
71
71
  inputSchema: {
72
72
  workspaceId: z.string().describe("The ID of the workspace to get types for")
73
73
  },
74
- annotations: { title: "Get Types in Workspace" }
74
+ annotations: { title: "List Types" }
75
75
  }, async ({ workspaceId }) => {
76
76
  try {
77
77
  const workspaces = await client.makeApiRequest('json/workspaces');
@@ -115,6 +115,58 @@ server.registerTool("cplace_get_type_datamodel", {
115
115
  };
116
116
  }
117
117
  });
118
+ server.registerTool("cplace_get_type_incoming_references", {
119
+ description: "Get comprehensive information about all incoming references to a specific type definition. Identifies all attributes from other types that can reference pages of the target type, including custom attributes and built-in attributes.",
120
+ inputSchema: {
121
+ workspaceId: z.string().describe("The ID of the workspace containing the target type definition"),
122
+ internalName: z.string().describe("The internal name of the target type definition")
123
+ },
124
+ annotations: { title: "Get Type Incoming References" }
125
+ }, async ({ workspaceId, internalName }) => {
126
+ debugLogWithTag('TYPE_REFS', `Starting incoming references request for type: ${internalName} in workspace: ${workspaceId}`);
127
+ try {
128
+ const result = await client.makeApiRequest('json/type/incoming-references', 'GET', {
129
+ workspaceId,
130
+ internalName
131
+ });
132
+ debugLogWithTag('TYPE_REFS', `Retrieved ${result.count || 0} incoming references for type: ${internalName}`);
133
+ return {
134
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
135
+ };
136
+ }
137
+ catch (error) {
138
+ debugLogWithTag('TYPE_REFS', `Error retrieving incoming references: ${error instanceof Error ? error.message : String(error)}`);
139
+ return {
140
+ content: [{ type: "text", text: `Error retrieving type incoming references: ${error instanceof Error ? error.message : String(error)}` }]
141
+ };
142
+ }
143
+ });
144
+ server.registerTool("cplace_get_page_incoming_references", {
145
+ description: "Get comprehensive information about all incoming references to a specific page. Identifies all pages that reference the target page through specific attributes, providing detailed information about the referencing pages and the attributes used for the references.",
146
+ inputSchema: {
147
+ pageUID: z.string().describe("The unique identifier (UID) of the target page"),
148
+ includeReferences: z.string().describe("JSON string specifying which references to include. Must be a JSON array containing objects with 'sourceType' and 'attributeName' properties, e.g. '[{\"sourceType\":\"cf.example.Project\",\"attributeName\":\"relatedTask\"}]'")
149
+ },
150
+ annotations: { title: "Get Page Incoming References" }
151
+ }, async ({ pageUID, includeReferences }) => {
152
+ debugLogWithTag('PAGE_REFS', `Starting incoming references request for page: ${pageUID}`);
153
+ try {
154
+ const result = await client.makeApiRequest('json/page/incoming-references', 'GET', {
155
+ pageUID,
156
+ includeReferences
157
+ });
158
+ debugLogWithTag('PAGE_REFS', `Retrieved ${result.totalCount || 0} incoming references for page: ${pageUID}`);
159
+ return {
160
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
161
+ };
162
+ }
163
+ catch (error) {
164
+ debugLogWithTag('PAGE_REFS', `Error retrieving incoming references: ${error instanceof Error ? error.message : String(error)}`);
165
+ return {
166
+ content: [{ type: "text", text: `Error retrieving page incoming references: ${error instanceof Error ? error.message : String(error)}` }]
167
+ };
168
+ }
169
+ });
118
170
  server.registerTool("cplace_get_page_by_id", {
119
171
  description: "Get comprehensive page information including relationships and metadata",
120
172
  inputSchema: {
@@ -123,7 +175,7 @@ server.registerTool("cplace_get_page_by_id", {
123
175
  annotations: { title: "Get Page by ID" }
124
176
  }, async ({ id }) => {
125
177
  try {
126
- const result = await client.makeApiRequest('json/page', 'GET', { id });
178
+ const result = await client.makeApiRequest('json/page', 'GET', { pageUID: id });
127
179
  return {
128
180
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
129
181
  };
@@ -153,12 +205,12 @@ server.registerTool("cplace_get_person_by_id", {
153
205
  };
154
206
  }
155
207
  });
156
- server.registerTool("cplace_search_person", {
157
- description: "Search a person by their name.",
208
+ server.registerTool("cplace_get_person_by_name", {
209
+ description: "Get details about a person by searching for their name.",
158
210
  inputSchema: {
159
211
  name: z.string().describe("The name of the person or part of the name to search for"),
160
212
  },
161
- annotations: { title: "Get Person by ID" }
213
+ annotations: { title: "Get Person by Name" }
162
214
  }, async ({ name }) => {
163
215
  try {
164
216
  const result = await client.makeApiRequest('json/person', 'GET', { name });
@@ -284,7 +336,8 @@ Empty/not empty checks:
284
336
  offset: result.pagination?.offset || offset,
285
337
  hasMore: result.pagination?.hasMore || false
286
338
  },
287
- humanReadableRepresentation: result.summary
339
+ humanReadableRepresentation: result.summary,
340
+ cplaceJson: result.cplaceJson
288
341
  };
289
342
  return {
290
343
  content: [{ type: "text", text: JSON.stringify(filteredResult, null, 2) }]
@@ -297,15 +350,15 @@ Empty/not empty checks:
297
350
  };
298
351
  }
299
352
  });
300
- server.registerTool("cplace_fulltext_search", {
301
- description: "Make a fulltext search across all pages in cplace. Use this only if the user is not asking about specific types.",
353
+ server.registerTool("cplace_search_pages_fulltext", {
354
+ description: "Make a fulltext search across all pages in cplace. Use this when searching for text content within pages.",
302
355
  inputSchema: {
303
356
  workspaceId: z.string().optional().describe("The ID of the workspace to search in. If not provided, searches across all accessible workspaces."),
304
- fulltext: z.string().describe("The fulltext search query to filter pages by. The results will include pages that conatin this string in their name, attributes, or content."),
357
+ fulltext: z.string().describe("The fulltext search query to filter pages by. The results will include pages that contain this string in their name, attributes, or content."),
305
358
  limit: z.number().min(1).max(1000).default(50).describe("Maximum number of results to return (1-1000)"),
306
359
  offset: z.number().min(0).max(10000).default(0).describe("Number of results to skip for pagination, cannot be larger than 10000"),
307
360
  },
308
- annotations: { title: "Search Pages" }
361
+ annotations: { title: "Search Pages by Fulltext" }
309
362
  }, async ({ workspaceId, fulltext, limit = 50, offset = 0, }) => {
310
363
  if (!fulltext) {
311
364
  return {
@@ -333,7 +386,8 @@ server.registerTool("cplace_fulltext_search", {
333
386
  offset: result.pagination?.offset || offset,
334
387
  hasMore: result.pagination?.hasMore || false
335
388
  },
336
- summary: result.summary
389
+ summary: result.summary,
390
+ cplaceJson: result.cplaceJson
337
391
  };
338
392
  return {
339
393
  content: [{ type: "text", text: JSON.stringify(filteredResult, null, 2) }]
@@ -345,15 +399,15 @@ server.registerTool("cplace_fulltext_search", {
345
399
  };
346
400
  }
347
401
  });
348
- server.registerTool("cplace_get_all_pages_of_type", {
349
- description: "Get all pages of a specific type in a workspace in cplace. ONLY use this if you need to retrieve all pages of a type.",
402
+ server.registerTool("cplace_list_pages_of_type", {
403
+ description: "List all pages of a specific type in a workspace in cplace. Use this to retrieve multiple pages of the same type.",
350
404
  inputSchema: {
351
405
  workspaceId: z.string().describe("The ID of the workspace to get pages from"),
352
406
  typeName: z.string().describe("The internal name of the type to retrieve pages for"),
353
407
  limit: z.number().min(1).max(1000).default(50).describe("Maximum number of results to return (1-1000)"),
354
408
  offset: z.number().min(0).max(10000).default(0).describe("Number of results to skip for pagination, cannot be larger than 10000"),
355
409
  },
356
- annotations: { title: "Get All Pages of Type" }
410
+ annotations: { title: "List Pages of Type" }
357
411
  }, async ({ workspaceId, typeName, limit = 50, offset = 0, }) => {
358
412
  try {
359
413
  const cplaceFilter = {
@@ -448,14 +502,19 @@ server.registerTool("cplace_search_pages_csv", {
448
502
  }
449
503
  });
450
504
  server.registerTool("cplace_list_widget_definitions", {
451
- description: "Get a list of all available widget definitions in the system with their basic metadata including names, descriptions, and apps",
452
- inputSchema: {},
505
+ description: "Get a list of all available widget definitions in the system that support the specified embedding context, with their basic metadata including names, descriptions, and apps",
506
+ inputSchema: {
507
+ embeddingContext: z.enum(["AS_WIDGET", "INSIDE_RICHSTRING", "INSIDE_WIDGET"]).default("AS_WIDGET").describe(`Filter widgets by embedding context support:
508
+ - AS_WIDGET (default): Widgets that support full widget embedding with frame and title
509
+ - INSIDE_RICHSTRING: Widgets that can be embedded in rich text content without frame
510
+ - INSIDE_WIDGET: Widgets that can be embedded inside other widgets without frame`)
511
+ },
453
512
  annotations: { title: "List Widget Definitions" }
454
- }, async () => {
455
- debugLogWithTag('WIDGETS', 'Starting widget definitions list request');
513
+ }, async ({ embeddingContext = "AS_WIDGET" }) => {
514
+ debugLogWithTag('WIDGETS', `Starting widget definitions list request with embeddingContext: ${embeddingContext}`);
456
515
  try {
457
- const result = await client.makeApiRequest('json/widget-definitions');
458
- debugLogWithTag('WIDGETS', `Retrieved widget definitions`);
516
+ const result = await client.makeApiRequest('json/widget-definitions', 'GET', { embeddingContext });
517
+ debugLogWithTag('WIDGETS', `Retrieved widget definitions for context: ${embeddingContext}`);
459
518
  return {
460
519
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
461
520
  };
@@ -490,13 +549,13 @@ server.registerTool("cplace_get_widget_definition", {
490
549
  server.registerTool("cplace_get_page_layout_overview", {
491
550
  description: "Get the high-level layout structure of a page including rows, columns, and widget summaries. Provides an overview of the page layout grid without detailed widget configurations.",
492
551
  inputSchema: {
493
- id: z.string().describe("The unique identifier (UID) of the page to get layout for, e.g. 'page/kkt8ol745jqur4581kelm5ply'")
552
+ pageUID: z.string().describe("The unique identifier (UID) of the page to get layout for, e.g. 'page/kkt8ol745jqur4581kelm5ply'")
494
553
  },
495
554
  annotations: { title: "Get Page Layout Overview" }
496
- }, async ({ id }) => {
497
- debugLogWithTag('LAYOUT', `Starting page layout overview request for: ${id}`);
555
+ }, async ({ pageUID }) => {
556
+ debugLogWithTag('LAYOUT', `Starting page layout overview request for: ${pageUID}`);
498
557
  try {
499
- const result = await client.makeApiRequest('json/pageLayout', 'GET', { id });
558
+ const result = await client.makeApiRequest('json/pageLayout', 'GET', { pageUID });
500
559
  const overview = {
501
560
  rows: result.rows?.map((row, rowIndex) => ({
502
561
  rowIndex,
@@ -511,28 +570,28 @@ server.registerTool("cplace_get_page_layout_overview", {
511
570
  })) || []
512
571
  })) || []
513
572
  };
514
- debugLogWithTag('LAYOUT', `Retrieved layout overview for: ${id}`);
573
+ debugLogWithTag('LAYOUT', `Retrieved page layout overview for: ${pageUID}`);
515
574
  return {
516
575
  content: [{ type: "text", text: JSON.stringify(overview, null, 2) }]
517
576
  };
518
577
  }
519
578
  catch (error) {
520
579
  return {
521
- content: [{ type: "text", text: `Error retrieving page layout overview for ${id}: ${error instanceof Error ? error.message : String(error)}` }]
580
+ content: [{ type: "text", text: `Error retrieving page layout overview for ${pageUID}: ${error instanceof Error ? error.message : String(error)}` }]
522
581
  };
523
582
  }
524
583
  });
525
584
  server.registerTool("cplace_get_widget_details", {
526
585
  description: "Get detailed configuration for a specific widget within a page layout. Use this after cplace_get_page_layout_overview to examine specific widget configurations.",
527
586
  inputSchema: {
528
- pageId: z.string().describe("The unique identifier (UID) of the page containing the widget"),
587
+ pageUID: z.string().describe("The unique identifier (UID) of the page containing the widget"),
529
588
  widgetId: z.string().describe("The unique widget identifier from the layout (e.g., 'id_123', 'id_456')")
530
589
  },
531
590
  annotations: { title: "Get Widget Details" }
532
- }, async ({ pageId, widgetId }) => {
533
- debugLogWithTag('LAYOUT', `Starting widget details request for widget ${widgetId} in page ${pageId}`);
591
+ }, async ({ pageUID, widgetId }) => {
592
+ debugLogWithTag('LAYOUT', `Starting widget details request for widget ${widgetId} in page ${pageUID}`);
534
593
  try {
535
- const result = await client.makeApiRequest('json/pageLayout', 'GET', { id: pageId });
594
+ const result = await client.makeApiRequest('json/pageLayout', 'GET', { pageUID });
536
595
  let foundWidget = null;
537
596
  const findWidget = (rows) => {
538
597
  for (const row of rows || []) {
@@ -554,17 +613,17 @@ server.registerTool("cplace_get_widget_details", {
554
613
  foundWidget = findWidget(result.rows);
555
614
  if (!foundWidget) {
556
615
  return {
557
- content: [{ type: "text", text: `Widget with ID ${widgetId} not found in page ${pageId}` }]
616
+ content: [{ type: "text", text: `Widget with ID ${widgetId} not found in page ${pageUID}` }]
558
617
  };
559
618
  }
560
- debugLogWithTag('LAYOUT', `Retrieved widget details for: ${widgetId}`);
619
+ debugLogWithTag('LAYOUT', `Retrieved widget details for: ${widgetId} in page`);
561
620
  return {
562
621
  content: [{ type: "text", text: JSON.stringify(foundWidget, null, 2) }]
563
622
  };
564
623
  }
565
624
  catch (error) {
566
625
  return {
567
- content: [{ type: "text", text: `Error retrieving widget details for ${widgetId} in page ${pageId}: ${error instanceof Error ? error.message : String(error)}` }]
626
+ content: [{ type: "text", text: `Error retrieving widget details for ${widgetId} in page ${pageUID}: ${error instanceof Error ? error.message : String(error)}` }]
568
627
  };
569
628
  }
570
629
  });
@@ -587,6 +646,504 @@ server.registerTool("cplace_get_current_user", {
587
646
  };
588
647
  }
589
648
  });
649
+ server.registerTool("cplace_create_page", {
650
+ description: "Create a new page with specified attributes and content using the POST /page/create endpoint",
651
+ inputSchema: {
652
+ workspaceId: z.string().describe("The ID of the workspace/space to create the page in (e.g., 'workspace/abc123')"),
653
+ typeInternalName: z.string().describe("The internal name of the page type (e.g., 'cf.example.myType')"),
654
+ name: z.string().describe("The name of the new page"),
655
+ parentUID: z.string().optional().describe("The UID of the parent page for hierarchy (optional)"),
656
+ content: z.string().optional().describe("Wiki markup content for the page (optional)"),
657
+ attributes: z.record(z.any()).optional().describe(`Custom attribute values as key-value pairs. Supports:
658
+ - String values: "text"
659
+ - Numeric values: 42 or 3.14
660
+ - Boolean values: true/false
661
+ - Date values: "2024-01-15" or "2024-01-15T10:30:00"
662
+ - Array values: ["value1", "value2"] for multi-valued attributes
663
+ - Reference values: "page/ref123" for page references
664
+ - Localized string values: {"en": "English text", "de": "German text", "fr": "French text"}
665
+ - Multi-valued localized strings: [{"en": "Text 1", "de": "Text 1"}, {"en": "Text 2", "de": "Text 2"}]
666
+
667
+ The system automatically detects attribute types from the page type definition and handles:
668
+ - Type-aware processing based on actual attribute definitions
669
+ - Proper multiplicity validation (single vs multi-valued)
670
+ - Automatic type conversion and validation
671
+ - Enhanced error messages with attribute-specific details`)
672
+ },
673
+ annotations: { title: "Create Page" }
674
+ }, async ({ workspaceId, typeInternalName, name, parentUID, content, attributes }) => {
675
+ debugLogWithTag('CREATE_PAGE', `Starting page creation: ${name} of type ${typeInternalName} in workspace ${workspaceId}`);
676
+ try {
677
+ const requestBody = {
678
+ workspaceId,
679
+ typeInternalName,
680
+ name
681
+ };
682
+ if (parentUID) {
683
+ requestBody.parentUID = parentUID;
684
+ debugLogWithTag('CREATE_PAGE', `Including parentUID: ${parentUID}`);
685
+ }
686
+ if (content) {
687
+ requestBody.content = content;
688
+ debugLogWithTag('CREATE_PAGE', `Including content (${content.length} chars)`);
689
+ }
690
+ if (attributes && Object.keys(attributes).length > 0) {
691
+ requestBody.attributes = attributes;
692
+ debugLogWithTag('CREATE_PAGE', `Including attributes: ${JSON.stringify(Object.keys(attributes))}`);
693
+ }
694
+ debugLogWithTag('CREATE_PAGE', `Request body: ${JSON.stringify(requestBody, null, 2)}`);
695
+ const result = await client.makeApiRequest('json/page/create', 'POST', undefined, requestBody);
696
+ debugLogWithTag('CREATE_PAGE', `Successfully created page: ${name}`);
697
+ return {
698
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
699
+ };
700
+ }
701
+ catch (error) {
702
+ debugLogWithTag('CREATE_PAGE', `Error creating page: ${error instanceof Error ? error.message : String(error)}`);
703
+ return {
704
+ content: [{ type: "text", text: `Error creating page: ${error instanceof Error ? error.message : String(error)}` }]
705
+ };
706
+ }
707
+ });
708
+ server.registerTool("cplace_update_page", {
709
+ description: "Update an existing page's attributes, content, or structure using the PATCH /page/{pageId} endpoint",
710
+ inputSchema: {
711
+ pageUID: z.string().describe("The unique identifier (UID) of the page to update"),
712
+ name: z.string().optional().describe("New name for the page (optional)"),
713
+ content: z.string().optional().describe("New wiki markup content (optional). Use empty string to remove content."),
714
+ parentUID: z.string().optional().describe("New parent page UID (optional - for moving pages). Use empty string to remove parent."),
715
+ attributes: z.record(z.any()).optional().describe(`Custom attributes to update as key-value pairs. Supports:
716
+ - String values: "text"
717
+ - Numeric values: 42 or 3.14
718
+ - Boolean values: true/false
719
+ - Date values: "2024-01-15" or "2024-01-15T10:30:00"
720
+ - Array values: ["value1", "value2"] for multi-valued attributes
721
+ - Reference values: "page/ref123" for page references
722
+ - Localized string values: {"en": "English text", "de": "German text", "fr": "French text"}
723
+ - Multi-valued localized strings: [{"en": "Text 1", "de": "Text 1"}, {"en": "Text 2", "de": "Text 2"}]
724
+ - Null values: null (removes the attribute value)
725
+ - Empty arrays: [] (removes all values for multi-valued attributes)
726
+ - New attributes: will be added to the page
727
+
728
+ The system automatically detects attribute types from the page type definition and handles:
729
+ - Type-aware processing based on actual attribute definitions
730
+ - Proper multiplicity validation (single vs multi-valued)
731
+ - Automatic type conversion and validation
732
+ - Enhanced error messages with attribute-specific details`)
733
+ },
734
+ annotations: { title: "Update Page" }
735
+ }, async ({ pageUID, name, content, parentUID, attributes }) => {
736
+ debugLogWithTag('UPDATE_PAGE', `Starting page update for: ${pageUID}`);
737
+ try {
738
+ const requestBody = {
739
+ pageUID
740
+ };
741
+ if (name !== undefined) {
742
+ requestBody.name = name;
743
+ }
744
+ if (content !== undefined) {
745
+ requestBody.content = content;
746
+ }
747
+ if (parentUID !== undefined) {
748
+ requestBody.parentUID = parentUID;
749
+ }
750
+ if (attributes && Object.keys(attributes).length > 0) {
751
+ requestBody.attributes = attributes;
752
+ }
753
+ if (Object.keys(requestBody).length === 1) {
754
+ return {
755
+ content: [{ type: "text", text: "No updates specified. At least one of name, content, parentId, or attributes must be provided." }]
756
+ };
757
+ }
758
+ const result = await client.makeApiRequest('json/page', 'PATCH', undefined, requestBody);
759
+ debugLogWithTag('UPDATE_PAGE', `Successfully updated page: ${pageUID}`);
760
+ return {
761
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
762
+ };
763
+ }
764
+ catch (error) {
765
+ debugLogWithTag('UPDATE_PAGE', `Error updating page: ${error instanceof Error ? error.message : String(error)}`);
766
+ return {
767
+ content: [{ type: "text", text: `Error updating page: ${error instanceof Error ? error.message : String(error)}` }]
768
+ };
769
+ }
770
+ });
771
+ server.registerTool("cplace_update_page_attributes", {
772
+ description: "Update page attributes (no content or structural changes) using the PATCH /page/{pageId} endpoint",
773
+ inputSchema: {
774
+ pageUID: z.string().describe("The unique identifier (UID) of the page to update"),
775
+ attributes: z.record(z.any()).describe(`Attribute updates as key-value pairs. Supports all the same data types as the full update tool:
776
+ - String values: "text"
777
+ - Numeric values: 42 or 3.14
778
+ - Boolean values: true/false
779
+ - Date values: "2024-01-15" or "2024-01-15T10:30:00"
780
+ - Array values: ["value1", "value2"] for multi-valued attributes
781
+ - Reference values: "page/ref123" for page references
782
+ - Localized string values: {"en": "English text", "de": "German text", "fr": "French text"}
783
+ - Multi-valued localized strings: [{"en": "Text 1", "de": "Text 1"}, {"en": "Text 2", "de": "Text 2"}]
784
+ - Null values: null (removes the attribute value)
785
+ - Empty arrays: [] (removes all values for multi-valued attributes)
786
+ - New attributes: will be added to the page
787
+
788
+ The system automatically detects attribute types from the page type definition and handles:
789
+ - Type-aware processing based on actual attribute definitions
790
+ - Proper multiplicity validation (single vs multi-valued)
791
+ - Automatic type conversion and validation
792
+ - Enhanced error messages with attribute-specific details`)
793
+ },
794
+ annotations: { title: "Update Page Attributes" }
795
+ }, async ({ pageUID, attributes }) => {
796
+ debugLogWithTag('UPDATE_PAGE_ATTRS', `Starting attribute-only update for: ${pageUID}`);
797
+ try {
798
+ if (!attributes || Object.keys(attributes).length === 0) {
799
+ return {
800
+ content: [{ type: "text", text: "No attributes specified for update. The attributes parameter must contain at least one attribute." }]
801
+ };
802
+ }
803
+ const requestBody = {
804
+ pageUID,
805
+ attributes: attributes
806
+ };
807
+ const result = await client.makeApiRequest('json/page', 'PATCH', undefined, requestBody);
808
+ debugLogWithTag('UPDATE_PAGE_ATTRS', `Successfully updated attributes for page: ${pageUID}`);
809
+ return {
810
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
811
+ };
812
+ }
813
+ catch (error) {
814
+ debugLogWithTag('UPDATE_PAGE_ATTRS', `Error updating page attributes: ${error instanceof Error ? error.message : String(error)}`);
815
+ return {
816
+ content: [{ type: "text", text: `Error updating page attributes: ${error instanceof Error ? error.message : String(error)}` }]
817
+ };
818
+ }
819
+ });
820
+ server.registerTool("cplace_add_widget_to_layout", {
821
+ description: "Add a new widget to a page layout at a specific position. Supports both page-level layouts (default) and embedded layouts within container widgets like AttributesGroup.",
822
+ inputSchema: {
823
+ pageUID: z.string().describe("The unique identifier (UID) of the page to add the widget to"),
824
+ widgetType: z.string().describe("The widget type identifier (e.g., 'cf.cplace.platform.table', 'cf.platform.wiki', 'cf.platform.singleAttribute')"),
825
+ configuration: z.record(z.any()).optional().describe(`Widget-specific configuration object as key-value pairs. IMPORTANT: Check the widget definition schema first to understand required data types:
826
+ - localizedString attributes: use {"en": "English text", "de": "German text"} format
827
+ - boolean attributes: use true/false
828
+ - number attributes: use numeric values
829
+ - string attributes: use simple strings
830
+
831
+ RECOMMENDED WORKFLOW:
832
+ 1. Use cplace_get_widget_definition() first to understand the configuration schema
833
+ 2. Match your configuration values to the constraint types defined in the schema
834
+ 3. Use appropriate data formats for each attribute type
835
+
836
+ Examples: {'title': {'en': 'My Widget', 'de': 'Mein Widget'}, 'showHeader': true, 'height': 600}
837
+ For embedded attribute widgets: {'cf.platform.quotedAttributeName': "'cf.cplace.myAttribute'", 'cf.platform.withLabel': true}`),
838
+ position: z.object({
839
+ rowIndex: z.number().min(0).describe("Row index in the layout (0-based)"),
840
+ columnIndex: z.number().min(0).describe("Column index within the row (0-based)"),
841
+ widgetIndex: z.number().min(0).describe("Widget index within the column (0-based)")
842
+ }).describe("Grid position where the widget should be added"),
843
+ layoutContext: z.enum(["page", "widget"]).default("page").describe("Layout context: 'page' for page-level layout (default), 'widget' for embedded layout within a container widget"),
844
+ containerWidgetId: z.string().optional().describe("Required when layoutContext is 'widget'. The unique identifier of the container widget (e.g., AttributesGroup widget) that contains the embedded layout to modify"),
845
+ layoutAttributeName: z.string().optional().describe("Optional layout attribute name for embedded layouts. If not provided, it will be auto-detected based on the container widget type")
846
+ },
847
+ annotations: { title: "Add Widget to Layout" }
848
+ }, async ({ pageUID, widgetType, configuration, position, layoutContext = "page", containerWidgetId, layoutAttributeName }) => {
849
+ debugLogWithTag('LAYOUT_MODIFY', `Starting add widget operation: ${widgetType} to ${layoutContext === "widget" ? "embedded layout in container " + containerWidgetId : "page layout"} ${pageUID} at position ${JSON.stringify(position)}`);
850
+ try {
851
+ if (layoutContext === "widget") {
852
+ if (!containerWidgetId) {
853
+ return {
854
+ content: [{ type: "text", text: "containerWidgetId is required when layoutContext is 'widget'" }]
855
+ };
856
+ }
857
+ debugLogWithTag('LAYOUT_MODIFY', `Adding to embedded layout in container widget: ${containerWidgetId}`);
858
+ }
859
+ const operation = {
860
+ type: "ADD",
861
+ widgetType,
862
+ position: {
863
+ rowIndex: position.rowIndex,
864
+ columnIndex: position.columnIndex,
865
+ widgetIndex: position.widgetIndex
866
+ }
867
+ };
868
+ if (configuration && Object.keys(configuration).length > 0) {
869
+ operation.configuration = configuration;
870
+ debugLogWithTag('LAYOUT_MODIFY', `Including configuration: ${JSON.stringify(Object.keys(configuration))}`);
871
+ }
872
+ const requestBody = {
873
+ pageUID,
874
+ layoutContext,
875
+ operation
876
+ };
877
+ if (layoutContext === "widget") {
878
+ requestBody.containerWidgetId = containerWidgetId;
879
+ if (layoutAttributeName) {
880
+ requestBody.layoutAttributeName = layoutAttributeName;
881
+ }
882
+ }
883
+ debugLogWithTag('LAYOUT_MODIFY', `Request body: ${JSON.stringify(requestBody, null, 2)}`);
884
+ const result = await client.makeApiRequest('json/pageLayout', 'PATCH', undefined, requestBody);
885
+ debugLogWithTag('LAYOUT_MODIFY', `Successfully added widget ${widgetType} to page ${pageUID}`);
886
+ return {
887
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
888
+ };
889
+ }
890
+ catch (error) {
891
+ debugLogWithTag('LAYOUT_MODIFY', `Error adding widget: ${error instanceof Error ? error.message : String(error)}`);
892
+ return {
893
+ content: [{ type: "text", text: `Error adding widget to layout: ${error instanceof Error ? error.message : String(error)}` }]
894
+ };
895
+ }
896
+ });
897
+ server.registerTool("cplace_remove_widget_from_layout", {
898
+ description: "Remove an existing widget from a page layout",
899
+ inputSchema: {
900
+ pageUID: z.string().describe("The unique identifier (UID) of the page containing the widget"),
901
+ widgetId: z.string().describe("The unique identifier of the widget to remove")
902
+ },
903
+ annotations: { title: "Remove Widget from Layout" }
904
+ }, async ({ pageUID, widgetId }) => {
905
+ debugLogWithTag('LAYOUT_MODIFY', `Starting remove widget operation: ${widgetId} from page ${pageUID}`);
906
+ try {
907
+ const requestBody = {
908
+ pageUID,
909
+ operation: {
910
+ type: "REMOVE",
911
+ widgetId
912
+ }
913
+ };
914
+ debugLogWithTag('LAYOUT_MODIFY', `Request body: ${JSON.stringify(requestBody, null, 2)}`);
915
+ const result = await client.makeApiRequest('json/pageLayout', 'PATCH', undefined, requestBody);
916
+ debugLogWithTag('LAYOUT_MODIFY', `Successfully removed widget ${widgetId} from page ${pageUID}`);
917
+ return {
918
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
919
+ };
920
+ }
921
+ catch (error) {
922
+ debugLogWithTag('LAYOUT_MODIFY', `Error removing widget: ${error instanceof Error ? error.message : String(error)}`);
923
+ return {
924
+ content: [{ type: "text", text: `Error removing widget from layout: ${error instanceof Error ? error.message : String(error)}` }]
925
+ };
926
+ }
927
+ });
928
+ server.registerTool("cplace_update_widget_in_layout", {
929
+ description: "Modify widget configuration or display properties",
930
+ inputSchema: {
931
+ pageUID: z.string().describe("The unique identifier (UID) of the page containing the widget"),
932
+ widgetId: z.string().describe("The unique identifier of the widget to update"),
933
+ newConfiguration: z.record(z.any()).optional().describe(`Updated widget configuration as key-value pairs (optional). IMPORTANT: Check the widget definition schema first to understand required data types:
934
+ - localizedString attributes: use {"en": "English text", "de": "German text"} format
935
+ - boolean attributes: use true/false
936
+ - number attributes: use numeric values
937
+ - string attributes: use simple strings
938
+
939
+ RECOMMENDED WORKFLOW:
940
+ 1. Use cplace_get_widget_definition() first to understand the configuration schema
941
+ 2. Match your configuration values to the constraint types defined in the schema
942
+ 3. Use appropriate data formats for each attribute type
943
+
944
+ Examples: {'title': {'en': 'My Widget', 'de': 'Mein Widget'}, 'showHeader': true, 'height': 600}`),
945
+ collapsed: z.boolean().optional().describe("Widget collapse state (optional)")
946
+ },
947
+ annotations: { title: "Update Widget in Layout" }
948
+ }, async ({ pageUID, widgetId, newConfiguration, collapsed }) => {
949
+ debugLogWithTag('LAYOUT_MODIFY', `Starting update widget operation: ${widgetId} in page ${pageUID}`);
950
+ try {
951
+ const operation = {
952
+ type: "UPDATE",
953
+ widgetId
954
+ };
955
+ if (newConfiguration && Object.keys(newConfiguration).length > 0) {
956
+ operation.newConfiguration = newConfiguration;
957
+ debugLogWithTag('LAYOUT_MODIFY', `Including new configuration: ${JSON.stringify(Object.keys(newConfiguration))}`);
958
+ }
959
+ if (collapsed !== undefined) {
960
+ operation.collapsed = collapsed;
961
+ debugLogWithTag('LAYOUT_MODIFY', `Setting collapsed state to: ${collapsed}`);
962
+ }
963
+ if (!operation.newConfiguration && operation.collapsed === undefined) {
964
+ return {
965
+ content: [{ type: "text", text: "No updates specified. At least one of newConfiguration or collapsed must be provided." }]
966
+ };
967
+ }
968
+ const requestBody = {
969
+ pageUID,
970
+ operation
971
+ };
972
+ debugLogWithTag('LAYOUT_MODIFY', `Request body: ${JSON.stringify(requestBody, null, 2)}`);
973
+ const result = await client.makeApiRequest('json/pageLayout', 'PATCH', undefined, requestBody);
974
+ debugLogWithTag('LAYOUT_MODIFY', `Successfully updated widget ${widgetId} in page ${pageUID}`);
975
+ return {
976
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
977
+ };
978
+ }
979
+ catch (error) {
980
+ debugLogWithTag('LAYOUT_MODIFY', `Error updating widget: ${error instanceof Error ? error.message : String(error)}`);
981
+ return {
982
+ content: [{ type: "text", text: `Error updating widget in layout: ${error instanceof Error ? error.message : String(error)}` }]
983
+ };
984
+ }
985
+ });
986
+ server.registerTool("cplace_move_widget_in_layout", {
987
+ description: "Relocate a widget to a different position in the layout",
988
+ inputSchema: {
989
+ pageUID: z.string().describe("The unique identifier (UID) of the page containing the widget"),
990
+ widgetId: z.string().describe("The unique identifier of the widget to move"),
991
+ newPosition: z.object({
992
+ rowIndex: z.number().min(0).describe("Target row index in the layout (0-based)"),
993
+ columnIndex: z.number().min(0).describe("Target column index within the row (0-based)"),
994
+ widgetIndex: z.number().min(0).describe("Target widget index within the column (0-based)")
995
+ }).describe("Target grid position for the widget")
996
+ },
997
+ annotations: { title: "Move Widget in Layout" }
998
+ }, async ({ pageUID, widgetId, newPosition }) => {
999
+ debugLogWithTag('LAYOUT_MODIFY', `Starting move widget operation: ${widgetId} in page ${pageUID} to position ${JSON.stringify(newPosition)}`);
1000
+ try {
1001
+ const requestBody = {
1002
+ pageUID,
1003
+ operation: {
1004
+ type: "MOVE",
1005
+ widgetId,
1006
+ newPosition: {
1007
+ rowIndex: newPosition.rowIndex,
1008
+ columnIndex: newPosition.columnIndex,
1009
+ widgetIndex: newPosition.widgetIndex
1010
+ }
1011
+ }
1012
+ };
1013
+ debugLogWithTag('LAYOUT_MODIFY', `Request body: ${JSON.stringify(requestBody, null, 2)}`);
1014
+ const result = await client.makeApiRequest('json/pageLayout', 'PATCH', undefined, requestBody);
1015
+ debugLogWithTag('LAYOUT_MODIFY', `Successfully moved widget ${widgetId} in page ${pageUID}`);
1016
+ return {
1017
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
1018
+ };
1019
+ }
1020
+ catch (error) {
1021
+ debugLogWithTag('LAYOUT_MODIFY', `Error moving widget: ${error instanceof Error ? error.message : String(error)}`);
1022
+ return {
1023
+ content: [{ type: "text", text: `Error moving widget in layout: ${error instanceof Error ? error.message : String(error)}` }]
1024
+ };
1025
+ }
1026
+ });
1027
+ server.registerTool("cplace_get_embedded_layout", {
1028
+ description: "Get the widget layout structure of an embedded widget within a container widget on a specific page. This endpoint accesses nested widget layouts embedded within other widgets, such as AttributesGroup widgets.",
1029
+ inputSchema: {
1030
+ pageUID: z.string().describe("The unique identifier (UID) of the page containing the container widget"),
1031
+ containerWidgetId: z.string().describe("The ID of the container widget that contains the embedded layout"),
1032
+ layoutAttributeName: z.string().describe("The name of the attribute that defines the embedded layout structure")
1033
+ },
1034
+ annotations: { title: "Get Embedded Layout" }
1035
+ }, async ({ pageUID, containerWidgetId, layoutAttributeName }) => {
1036
+ debugLogWithTag('EMBEDDED_LAYOUT', `Starting embedded widget layout request for container ${containerWidgetId} in page ${pageUID}`);
1037
+ try {
1038
+ const result = await client.makeApiRequest('json/embeddedWidgetLayout', 'GET', {
1039
+ pageUID,
1040
+ containerWidgetId,
1041
+ layoutAttributeName
1042
+ });
1043
+ debugLogWithTag('EMBEDDED_LAYOUT', `Retrieved embedded widget layout for container: ${containerWidgetId}`);
1044
+ return {
1045
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
1046
+ };
1047
+ }
1048
+ catch (error) {
1049
+ return {
1050
+ content: [{ type: "text", text: `Error retrieving embedded widget layout: ${error instanceof Error ? error.message : String(error)}` }]
1051
+ };
1052
+ }
1053
+ });
1054
+ server.registerTool("cplace_add_widget_to_embedded_layout", {
1055
+ description: "Add a new widget to an embedded layout within a container widget. This enables adding widgets to nested layouts like those within AttributesGroup widgets.",
1056
+ inputSchema: {
1057
+ pageUID: z.string().describe("The unique identifier (UID) of the page containing the container widget"),
1058
+ containerWidgetId: z.string().describe("The ID of the container widget that contains the embedded layout to modify"),
1059
+ layoutAttributeName: z.string().describe("The name of the attribute that defines the embedded layout structure"),
1060
+ widgetType: z.string().describe("Widget type identifier (e.g., 'cf.cplace.platform.table', 'cf.platform.wiki')"),
1061
+ position: z.object({
1062
+ rowIndex: z.number().min(0).describe("Row index in the embedded layout (0-based)"),
1063
+ columnIndex: z.number().min(0).describe("Column index within the row (0-based)"),
1064
+ widgetIndex: z.number().min(0).describe("Widget index within the column (0-based)")
1065
+ }).describe("Position where the widget should be added in the embedded layout"),
1066
+ configuration: z.record(z.any()).optional().describe(`Widget configuration as key-value pairs. IMPORTANT: Check the widget definition schema first to understand required data types:
1067
+ - localizedString attributes: use {"en": "English text", "de": "German text"} format
1068
+ - boolean attributes: use true/false
1069
+ - number attributes: use numeric values
1070
+ - string attributes: use simple strings
1071
+
1072
+ RECOMMENDED WORKFLOW:
1073
+ 1. Use cplace_get_widget_definition() first to understand the configuration schema
1074
+ 2. Match your configuration values to the constraint types defined in the schema
1075
+ 3. Use appropriate data formats for each attribute type
1076
+
1077
+ Examples: {'title': {'en': 'Embedded Widget', 'de': 'Eingebettetes Widget'}, 'showHeader': true}`)
1078
+ },
1079
+ annotations: { title: "Add Widget to Embedded Layout" }
1080
+ }, async ({ pageUID, containerWidgetId, layoutAttributeName, widgetType, position, configuration }) => {
1081
+ debugLogWithTag('EMBEDDED_LAYOUT_ADD', `Starting add widget to embedded layout: ${widgetType} to container ${containerWidgetId} in page ${pageUID}`);
1082
+ try {
1083
+ const operation = {
1084
+ type: "ADD",
1085
+ widgetType,
1086
+ position
1087
+ };
1088
+ if (configuration && Object.keys(configuration).length > 0) {
1089
+ operation.configuration = configuration;
1090
+ debugLogWithTag('EMBEDDED_LAYOUT_ADD', `Including configuration: ${JSON.stringify(Object.keys(configuration))}`);
1091
+ }
1092
+ const requestBody = {
1093
+ pageUID,
1094
+ containerWidgetId,
1095
+ layoutAttributeName,
1096
+ operation
1097
+ };
1098
+ debugLogWithTag('EMBEDDED_LAYOUT_ADD', `Request body: ${JSON.stringify(requestBody, null, 2)}`);
1099
+ const result = await client.makeApiRequest('json/embeddedWidgetLayout', 'PATCH', undefined, requestBody);
1100
+ debugLogWithTag('EMBEDDED_LAYOUT_ADD', `Successfully added widget ${widgetType} to embedded layout`);
1101
+ return {
1102
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
1103
+ };
1104
+ }
1105
+ catch (error) {
1106
+ debugLogWithTag('EMBEDDED_LAYOUT_ADD', `Error adding widget to embedded layout: ${error instanceof Error ? error.message : String(error)}`);
1107
+ return {
1108
+ content: [{ type: "text", text: `Error adding widget to embedded layout: ${error instanceof Error ? error.message : String(error)}` }]
1109
+ };
1110
+ }
1111
+ });
1112
+ server.registerTool("cplace_remove_widget_from_embedded_layout", {
1113
+ description: "Remove an existing widget from an embedded layout within a container widget. This enables removing widgets from nested layouts like those within AttributesGroup widgets.",
1114
+ inputSchema: {
1115
+ pageUID: z.string().describe("The unique identifier (UID) of the page containing the container widget"),
1116
+ containerWidgetId: z.string().describe("The ID of the container widget that contains the embedded layout to modify"),
1117
+ layoutAttributeName: z.string().describe("The name of the attribute that defines the embedded layout structure"),
1118
+ widgetId: z.string().describe("The ID of the existing widget to remove from the embedded layout")
1119
+ },
1120
+ annotations: { title: "Remove Widget from Embedded Layout" }
1121
+ }, async ({ pageUID, containerWidgetId, layoutAttributeName, widgetId }) => {
1122
+ debugLogWithTag('EMBEDDED_LAYOUT_REMOVE', `Starting remove widget from embedded layout: ${widgetId} from container ${containerWidgetId} in page ${pageUID}`);
1123
+ try {
1124
+ const requestBody = {
1125
+ pageUID,
1126
+ containerWidgetId,
1127
+ layoutAttributeName,
1128
+ operation: {
1129
+ type: "REMOVE",
1130
+ widgetId
1131
+ }
1132
+ };
1133
+ debugLogWithTag('EMBEDDED_LAYOUT_REMOVE', `Request body: ${JSON.stringify(requestBody, null, 2)}`);
1134
+ const result = await client.makeApiRequest('json/embeddedWidgetLayout', 'PATCH', undefined, requestBody);
1135
+ debugLogWithTag('EMBEDDED_LAYOUT_REMOVE', `Successfully removed widget ${widgetId} from embedded layout`);
1136
+ return {
1137
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
1138
+ };
1139
+ }
1140
+ catch (error) {
1141
+ debugLogWithTag('EMBEDDED_LAYOUT_REMOVE', `Error removing widget from embedded layout: ${error instanceof Error ? error.message : String(error)}`);
1142
+ return {
1143
+ content: [{ type: "text", text: `Error removing widget from embedded layout: ${error instanceof Error ? error.message : String(error)}` }]
1144
+ };
1145
+ }
1146
+ });
590
1147
  async function runServer() {
591
1148
  const transport = new StdioServerTransport();
592
1149
  await server.connect(transport);