#
tokens: 23646/50000 24/26 files (page 1/3)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 3. Use http://codebase.md/r3-yamauchi/kintone-mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .gitignore
├── .mcpbignore
├── .npmrc
├── AGENTS.md
├── CLAUDE.md
├── docs
│   ├── images
│   │   ├── mcpb-install0.png
│   │   ├── mcpb-install1.png
│   │   ├── mcpb-install2.png
│   │   ├── mcpb-install3.png
│   │   ├── mcpb-install4.png
│   │   ├── mcpb-install5.png
│   │   ├── mcpb-install6.png
│   │   ├── mcpb-install7.png
│   │   ├── tool.png
│   │   └── tools.png
│   ├── mcp-server-architecture.md
│   ├── mcp-specification
│   │   ├── 2024-11-05.txt
│   │   └── 2025-03-26.txt
│   └── tool-annotations.md
├── LICENSE
├── manifest.json
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── README.md
├── renovate.json
├── scripts
│   └── ensure-pnpm.js
├── server.js
├── src
│   ├── constants.js
│   ├── index.js
│   ├── models
│   │   ├── KintoneCredentials.js
│   │   └── KintoneRecord.js
│   ├── repositories
│   │   ├── base
│   │   │   ├── BaseKintoneRepository.js
│   │   │   └── http
│   │   │       ├── createKintoneClient.js
│   │   │       ├── KintoneApiError.js
│   │   │       └── KintoneHttpClient.js
│   │   ├── KintoneAppRepository.js
│   │   ├── KintoneFileRepository.js
│   │   ├── KintoneFormRepository.js
│   │   ├── KintonePreviewRepository.js
│   │   ├── KintoneRecordRepository.js
│   │   ├── KintoneRepository.js
│   │   ├── KintoneSpaceRepository.js
│   │   ├── KintoneUserRepository.js
│   │   └── validators
│   │       ├── FieldValidator.js
│   │       ├── LayoutValidator.js
│   │       └── OptionValidator.js
│   ├── server
│   │   ├── handlers
│   │   │   ├── ErrorHandlers.js
│   │   │   ├── ToolRequestHandler.js
│   │   │   └── ToolRouter.js
│   │   ├── MCPServer.js
│   │   └── tools
│   │       ├── AppTools.js
│   │       ├── BaseToolHandler.js
│   │       ├── definitions
│   │       │   ├── AppToolDefinitions.js
│   │       │   ├── DocumentationToolDefinitions.js
│   │       │   ├── FieldToolDefinitions.js
│   │       │   ├── FileToolDefinitions.js
│   │       │   ├── index.js
│   │       │   ├── LayoutToolDefinitions.js
│   │       │   ├── RecordToolDefinitions.js
│   │       │   ├── SpaceToolDefinitions.js
│   │       │   ├── SystemToolDefinitions.js
│   │       │   └── UserToolDefinitions.js
│   │       ├── documentation
│   │       │   ├── calcField.js
│   │       │   ├── choiceFields.js
│   │       │   ├── dateField.js
│   │       │   ├── dateTimeField.js
│   │       │   ├── fileField.js
│   │       │   ├── index.js
│   │       │   ├── layoutFields.js
│   │       │   ├── linkField.js
│   │       │   ├── lookup.js
│   │       │   ├── numberField.js
│   │       │   ├── otherFields.js
│   │       │   ├── queryLanguage.js
│   │       │   ├── referenceTable.js
│   │       │   ├── richText.js
│   │       │   ├── subtable.js
│   │       │   ├── systemFields.js
│   │       │   ├── textFields.js
│   │       │   ├── timeField.js
│   │       │   └── userSelect.js
│   │       ├── DocumentationTools.js
│   │       ├── FieldTools.js
│   │       ├── FileTools.js
│   │       ├── LayoutTools.js
│   │       ├── RecordTools.js
│   │       ├── SpaceTools.js
│   │       ├── SystemTools.js
│   │       └── UserTools.js
│   └── utils
│       ├── DataTransformers.js
│       ├── FieldValidationUtils.js
│       ├── index.js
│       ├── LayoutUtils.js
│       ├── LoggingUtils.js
│       ├── ResponseBuilder.js
│       └── ValidationUtils.js
└── unofficial-kintone-mcp-server-8.0.0.mcpb
```

# Files

--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------

```
1 | minimumReleaseAge=1440
2 | 
```

--------------------------------------------------------------------------------
/.mcpbignore:
--------------------------------------------------------------------------------

```
1 | .env
2 | .github/
3 | docs/
4 | AGENTS.md
5 | CLAUDE.md
6 | llms-install.md
7 | renovate.json
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
  1 | # Package manager configuration
  2 | .pnpm-store/
  3 | 
  4 | # Standalone build artifacts
  5 | standalone/
  6 | standalone-package/
  7 | *.tgz
  8 | 
  9 | # Test directories
 10 | test-package/
 11 | coverage/
 12 | 
 13 | # Logs
 14 | logs
 15 | *.log
 16 | npm-debug.log*
 17 | yarn-debug.log*
 18 | yarn-error.log*
 19 | lerna-debug.log*
 20 | .pnpm-debug.log*
 21 | 
 22 | # Diagnostic reports (https://nodejs.org/api/report.html)
 23 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
 24 | 
 25 | # Runtime data
 26 | pids
 27 | *.pid
 28 | *.seed
 29 | *.pid.lock
 30 | 
 31 | # Directory for instrumented libs generated by jscoverage/JSCover
 32 | lib-cov
 33 | 
 34 | # Coverage directory used by tools like istanbul
 35 | coverage
 36 | *.lcov
 37 | 
 38 | # nyc test coverage
 39 | .nyc_output
 40 | 
 41 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
 42 | .grunt
 43 | 
 44 | # Bower dependency directory (https://bower.io/)
 45 | bower_components
 46 | 
 47 | # node-waf configuration
 48 | .lock-wscript
 49 | 
 50 | # Compiled binary addons (https://nodejs.org/api/addons.html)
 51 | build/Release
 52 | 
 53 | # Dependency directories
 54 | node_modules/
 55 | jspm_packages/
 56 | 
 57 | # Snowpack dependency directory (https://snowpack.dev/)
 58 | web_modules/
 59 | 
 60 | # TypeScript cache
 61 | *.tsbuildinfo
 62 | 
 63 | # Optional npm cache directory
 64 | .npm
 65 | 
 66 | # Optional eslint cache
 67 | .eslintcache
 68 | 
 69 | # Optional stylelint cache
 70 | .stylelintcache
 71 | 
 72 | # Microbundle cache
 73 | .rpt2_cache/
 74 | .rts2_cache_cjs/
 75 | .rts2_cache_es/
 76 | .rts2_cache_umd/
 77 | 
 78 | # Optional REPL history
 79 | .node_repl_history
 80 | 
 81 | # Output of 'npm pack'
 82 | *.tgz
 83 | 
 84 | # Yarn Integrity file
 85 | .yarn-integrity
 86 | 
 87 | # dotenv environment variable files
 88 | .env
 89 | .env.development.local
 90 | .env.test.local
 91 | .env.production.local
 92 | .env.local
 93 | 
 94 | # parcel-bundler cache (https://parceljs.org/)
 95 | .cache
 96 | .parcel-cache
 97 | 
 98 | # Next.js build output
 99 | .next
100 | out
101 | 
102 | # Nuxt.js build / generate output
103 | .nuxt
104 | dist
105 | 
106 | # Gatsby files
107 | .cache/
108 | # Comment in the public line in if your project uses Gatsby and not Next.js
109 | # https://nextjs.org/blog/next-9-1#public-directory-support
110 | # public
111 | 
112 | # vuepress build output
113 | .vuepress/dist
114 | 
115 | # vuepress v2.x temp and cache directory
116 | .temp
117 | .cache
118 | 
119 | # Docusaurus cache and generated files
120 | .docusaurus
121 | 
122 | # Serverless directories
123 | .serverless/
124 | 
125 | # FuseBox cache
126 | .fusebox/
127 | 
128 | # DynamoDB Local files
129 | .dynamodb/
130 | 
131 | # TernJS port file
132 | .tern-port
133 | 
134 | # Stores VSCode versions used for testing VSCode extensions
135 | .vscode-test
136 | 
137 | # yarn v2
138 | .yarn/cache
139 | .yarn/unplugged
140 | .yarn/build-state.yml
141 | .yarn/install-state.gz
142 | .pnp.*
143 | 
144 | # Project specific files
145 | .clinerules
146 | .clinerules/
147 | 
148 | # Editor directories and files
149 | .idea/
150 | .vscode/
151 | *.suo
152 | *.ntvs*
153 | *.njsproj
154 | *.sln
155 | *.sw?
156 | 
157 | # OS generated files
158 | .DS_Store
159 | .DS_Store?
160 | ._*
161 | .Spotlight-V100
162 | .Trashes
163 | ehthumbs.db
164 | Thumbs.db
165 | 
166 | # Local config files
167 | *.local.js
168 | config.local.js
169 | 
```

--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------

```javascript
1 | #!/usr/bin/env node
2 | import './src/index.js';
3 | 
```

--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------

```json
1 | {
2 |   "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 |   "extends": [
4 |     "config:recommended"
5 |   ]
6 | }
7 | 
```

--------------------------------------------------------------------------------
/src/models/KintoneRecord.js:
--------------------------------------------------------------------------------

```javascript
1 | // src/models/KintoneRecord.js
2 | export class KintoneRecord {
3 |     constructor(appId, recordId, fields) {
4 |         this.appId = appId;
5 |         this.recordId = recordId;
6 |         this.fields = fields;
7 |     }
8 | }
9 | 
```

--------------------------------------------------------------------------------
/src/models/KintoneCredentials.js:
--------------------------------------------------------------------------------

```javascript
 1 | // src/models/KintoneCredentials.js
 2 | export class KintoneCredentials {
 3 |     constructor(domain, username, password) {
 4 |         this.domain = domain;
 5 |         this.username = username;
 6 |         this.password = password;
 7 |         this.auth = Buffer.from(`${username}:${password}`).toString('base64');
 8 |     }
 9 | }
10 | 
```

--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------

```javascript
1 | // src/utils/index.js
2 | export { ValidationUtils } from './ValidationUtils.js';
3 | export { LoggingUtils } from './LoggingUtils.js';
4 | export { ResponseBuilder } from './ResponseBuilder.js';
5 | export { FieldValidationUtils } from './FieldValidationUtils.js';
6 | export { DataTransformers } from './DataTransformers.js';
7 | export { LayoutUtils } from './LayoutUtils.js';
```

--------------------------------------------------------------------------------
/src/repositories/base/http/KintoneApiError.js:
--------------------------------------------------------------------------------

```javascript
 1 | // src/repositories/base/http/KintoneApiError.js
 2 | export class KintoneApiError extends Error {
 3 |     constructor(message, { status = null, code = null, errors = null, responseBody = null } = {}) {
 4 |         super(message);
 5 |         this.name = 'KintoneApiError';
 6 |         this.status = status;
 7 |         this.code = code;
 8 |         this.errors = errors;
 9 |         this.responseBody = responseBody;
10 |     }
11 | }
12 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "unofficial-kintone-mcp-server",
 3 |   "version": "8.0.0",
 4 |   "packageManager": "[email protected]",
 5 |   "main": "server.js",
 6 |   "type": "module",
 7 |   "scripts": {
 8 |     "start": "node server.js",
 9 |     "test": "node --test"
10 |   },
11 |   "keywords": [
12 |     "modelcontextprotocol",
13 |     "mcp",
14 |     "kintone"
15 |   ],
16 |   "author": {
17 |     "name": "r3-yamauchi",
18 |     "url": "https://www.r3it.com/blog/author/yamauchi"
19 |   },
20 |   "license": "AGPL-3.0",
21 |   "description": "MCP server for kintone (Unofficial)",
22 |   "engines": {
23 |     "node": ">=20"
24 |   },
25 |   "dependencies": {
26 |     "@modelcontextprotocol/sdk": "1.18.2"
27 |   }
28 | }
29 | 
```

--------------------------------------------------------------------------------
/src/utils/DataTransformers.js:
--------------------------------------------------------------------------------

```javascript
 1 | import { LoggingUtils } from './LoggingUtils.js';
 2 | 
 3 | export function convertDropdownFieldType(obj) {
 4 |     if (!obj || typeof obj !== 'object') return;
 5 |     
 6 |     for (const key in obj) {
 7 |         if (Object.prototype.hasOwnProperty.call(obj, key)) {
 8 |             const value = obj[key];
 9 |             
10 |             if (key === 'type' && value === 'DROPDOWN') {
11 |                 obj[key] = 'DROP_DOWN';
12 |                 LoggingUtils.debug('field', 'dropdown_type_normalized', { property: key });
13 |             }
14 |             else if (key === 'field_type' && value === 'DROPDOWN') {
15 |                 obj[key] = 'DROP_DOWN';
16 |                 LoggingUtils.debug('field', 'dropdown_type_normalized', { property: key });
17 |             }
18 |             else if (value && typeof value === 'object') {
19 |                 convertDropdownFieldType(value);
20 |             }
21 |         }
22 |     }
23 | }
24 | 
```

--------------------------------------------------------------------------------
/src/server/tools/definitions/index.js:
--------------------------------------------------------------------------------

```javascript
 1 | // src/server/tools/definitions/index.js
 2 | 
 3 | import { recordToolDefinitions } from './RecordToolDefinitions.js';
 4 | import { appToolDefinitions } from './AppToolDefinitions.js';
 5 | import { spaceToolDefinitions } from './SpaceToolDefinitions.js';
 6 | import { fieldToolDefinitions } from './FieldToolDefinitions.js';
 7 | import { documentationToolDefinitions } from './DocumentationToolDefinitions.js';
 8 | import { layoutToolDefinitions } from './LayoutToolDefinitions.js';
 9 | import { userToolDefinitions } from './UserToolDefinitions.js';
10 | import { systemToolDefinitions } from './SystemToolDefinitions.js';
11 | import { fileToolDefinitions } from './FileToolDefinitions.js';
12 | 
13 | /**
14 |  * 全てのツール定義をフラットな配列として提供
15 |  */
16 | export const allToolDefinitions = [
17 |     ...recordToolDefinitions,
18 |     ...appToolDefinitions,
19 |     ...spaceToolDefinitions,
20 |     ...fieldToolDefinitions,
21 |     ...documentationToolDefinitions,
22 |     ...layoutToolDefinitions,
23 |     ...userToolDefinitions,
24 |     ...systemToolDefinitions,
25 |     ...fileToolDefinitions
26 | ];
27 | 
```

--------------------------------------------------------------------------------
/src/server/tools/UserTools.js:
--------------------------------------------------------------------------------

```javascript
 1 | // src/server/tools/UserTools.js
 2 | import { ValidationUtils } from '../../utils/ValidationUtils.js';
 3 | import { LoggingUtils } from '../../utils/LoggingUtils.js';
 4 | import { ResponseBuilder } from '../../utils/ResponseBuilder.js';
 5 | 
 6 | // ユーザー関連のツールを処理する関数
 7 | export async function handleUserTools(name, args, repository) {
 8 |     // 共通のツール実行ログ
 9 |     LoggingUtils.logToolExecution('user', name, args);
10 |     switch (name) {
11 |         case 'get_users': {
12 |             const codes = args.codes || [];
13 |             
14 |             if (codes.length > 0) {
15 |                 ValidationUtils.validateArray(codes, 'codes');
16 |             }
17 |             
18 |             return repository.getUsers(codes);
19 |         }
20 |         
21 |         case 'get_groups': {
22 |             const codes = args.codes || [];
23 |             
24 |             if (codes.length > 0) {
25 |                 ValidationUtils.validateArray(codes, 'codes');
26 |             }
27 |             
28 |             return repository.getGroups(codes);
29 |         }
30 |         
31 |         case 'get_group_users': {
32 |             ValidationUtils.validateRequired(args, ['group_code']);
33 |             ValidationUtils.validateString(args.group_code, 'group_code');
34 |             
35 |             return repository.getGroupUsers(args.group_code);
36 |         }
37 |         
38 |         default:
39 |             throw new Error(`Unknown user tool: ${name}`);
40 |     }
41 | }
42 | 
```

--------------------------------------------------------------------------------
/src/repositories/KintoneFileRepository.js:
--------------------------------------------------------------------------------

```javascript
 1 | // src/repositories/KintoneFileRepository.js
 2 | import { BaseKintoneRepository } from './base/BaseKintoneRepository.js';
 3 | import { LoggingUtils } from '../utils/LoggingUtils.js';
 4 | 
 5 | export class KintoneFileRepository extends BaseKintoneRepository {
 6 |     async uploadFile(fileName, fileData) {
 7 |         try {
 8 |             LoggingUtils.logDetailedOperation('uploadFile', 'ファイルアップロード開始', { fileName });
 9 |             const buffer = Buffer.from(fileData, 'base64');
10 |             const response = await this.client.file.uploadFile({
11 |                 file: {
12 |                     name: fileName,
13 |                     data: buffer
14 |                 }
15 |             });
16 |             LoggingUtils.logDetailedOperation('uploadFile', 'ファイルアップロード完了', { 
17 |                 fileName,
18 |                 fileKey: response.fileKey 
19 |             });
20 |             return response;
21 |         } catch (error) {
22 |             this.handleKintoneError(error, `upload file ${fileName}`);
23 |         }
24 |     }
25 | 
26 |     async downloadFile(fileKey) {
27 |         try {
28 |             LoggingUtils.logDetailedOperation('downloadFile', 'ファイルダウンロード開始', { fileKey });
29 |             const response = await this.client.file.downloadFile({ fileKey: fileKey });
30 |             LoggingUtils.logDetailedOperation('downloadFile', 'ファイルダウンロード完了', { 
31 |                 fileKey,
32 |                 contentType: response.contentType || 'unknown' 
33 |             });
34 |             return response;
35 |         } catch (error) {
36 |             this.handleKintoneError(error, `download file with key ${fileKey}`);
37 |         }
38 |     }
39 | }
40 | 
```

--------------------------------------------------------------------------------
/src/server/handlers/ToolRequestHandler.js:
--------------------------------------------------------------------------------

```javascript
 1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
 2 | import { ToolRouter } from './ToolRouter.js';
 3 | import { convertDropdownFieldType } from '../../utils/DataTransformers.js';
 4 | import { handleToolError } from './ErrorHandlers.js';
 5 | import { LoggingUtils } from '../../utils/LoggingUtils.js';
 6 | 
 7 | export async function executeToolRequest(request, repository) {
 8 |     const { name, arguments: args } = request.params;
 9 |     LoggingUtils.info('tool', 'tool_request_received', { name });
10 |     LoggingUtils.debug('tool', 'tool_request_payload', request.params);
11 |     
12 |     if (!name) {
13 |         throw new McpError(
14 |             ErrorCode.InvalidParams,
15 |             `ツール名が指定されていません。`
16 |         );
17 |     }
18 |     
19 |     if (!args) {
20 |         throw new McpError(
21 |             ErrorCode.InvalidParams,
22 |             `ツール "${name}" の引数が指定されていません。`
23 |         );
24 |     }
25 |     
26 |     convertDropdownFieldType(args);
27 |     
28 |     LoggingUtils.info('tool', 'tool_execution_start', { name });
29 |     LoggingUtils.debug('tool', 'tool_arguments', args);
30 | 
31 |     try {
32 |         const router = new ToolRouter();
33 |         
34 |         const lookupResponse = router.handleLookupFieldSpecialCase(name, args, repository);
35 |         if (lookupResponse) {
36 |             return await lookupResponse;
37 |         }
38 |         
39 |         const result = await router.routeToolRequest(name, args, repository);
40 |         
41 |         return {
42 |             content: [
43 |                 {
44 |                     type: 'text',
45 |                     text: JSON.stringify(result, null, 2)
46 |                 }
47 |             ]
48 |         };
49 |     } catch (error) {
50 |         return handleToolError(error);
51 |     }
52 | }
53 | 
```

--------------------------------------------------------------------------------
/src/server/MCPServer.js:
--------------------------------------------------------------------------------

```javascript
 1 | // src/server/MCPServer.js
 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
 4 | import {
 5 |     CallToolRequestSchema,
 6 |     ListToolsRequestSchema,
 7 |     ListPromptsRequestSchema,
 8 |     ListResourcesRequestSchema,
 9 |     GetPromptRequestSchema,
10 |     ListResourceTemplatesRequestSchema,
11 |     ReadResourceRequestSchema,
12 |     McpError,
13 |     ErrorCode,
14 | } from '@modelcontextprotocol/sdk/types.js';
15 | import { KintoneCredentials } from '../models/KintoneCredentials.js';
16 | import { KintoneRepository } from '../repositories/KintoneRepository.js';
17 | import { executeToolRequest } from './handlers/ToolRequestHandler.js';
18 | import { allToolDefinitions } from './tools/definitions/index.js';
19 | import { LoggingUtils } from '../utils/LoggingUtils.js';
20 | 
21 | export class MCPServer {
22 |     constructor(domain, username, password) {
23 |         this.credentials = new KintoneCredentials(domain, username, password);
24 |         this.repository = new KintoneRepository(this.credentials);
25 |         
26 |         this.server = new Server(
27 |             {
28 |                 name: 'kintonemcp',
29 |                 version: '8.0.0',
30 |             },
31 |             {
32 |                 capabilities: {
33 |                     tools: {},
34 |                     prompts: {},
35 |                     resources: {},
36 |                 },
37 |             }
38 |         );
39 |         
40 |         this.setupRequestHandlers();
41 |         
42 |         // エラーハンドリング
43 |         this.server.onerror = (error) => LoggingUtils.error('server', 'mcp_server_error', error);
44 |         process.on('SIGINT', async () => {
45 |             await this.server.close();
46 |             process.exit(0);
47 |         });
48 |     }
49 |     
50 |     setupRequestHandlers() {
51 |         // ツール一覧を返すハンドラー
52 |         this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
53 |             tools: allToolDefinitions
54 |         }));
55 |         
56 |         // プロンプト一覧(未提供のため空配列)
57 |         this.server.setRequestHandler(ListPromptsRequestSchema, async () => ({
58 |             prompts: []
59 |         }));
60 |         this.server.setRequestHandler(GetPromptRequestSchema, async () => {
61 |             throw new McpError(ErrorCode.MethodNotFound, 'promptは提供されていません');
62 |         });
63 | 
64 |         // リソース一覧(未提供のため空配列)
65 |         this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
66 |             resources: []
67 |         }));
68 |         this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
69 |             resourceTemplates: []
70 |         }));
71 |         this.server.setRequestHandler(ReadResourceRequestSchema, async () => {
72 |             throw new McpError(ErrorCode.MethodNotFound, 'resourceは提供されていません');
73 |         });
74 |         
75 |         // ツールリクエストを実行するハンドラー
76 |         this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
77 |             return executeToolRequest(request, this.repository);
78 |         });
79 |     }
80 |     
81 |     async run() {
82 |         const transport = new StdioServerTransport();
83 |         await this.server.connect(transport);
84 |         LoggingUtils.info('server', 'mcp_server_running');
85 |     }
86 | }
87 | 
```

--------------------------------------------------------------------------------
/src/server/tools/SpaceTools.js:
--------------------------------------------------------------------------------

```javascript
  1 | // src/server/tools/SpaceTools.js
  2 | import { ValidationUtils } from '../../utils/ValidationUtils.js';
  3 | import { LoggingUtils } from '../../utils/LoggingUtils.js';
  4 | import { ResponseBuilder } from '../../utils/ResponseBuilder.js';
  5 | 
  6 | // スペース関連のツールを処理する関数
  7 | export async function handleSpaceTools(name, args, repository) {
  8 |     // 共通のツール実行ログ
  9 |     LoggingUtils.logToolExecution('space', name, args);
 10 |     
 11 |     switch (name) {
 12 |         case 'get_space': {
 13 |             ValidationUtils.validateRequired(args, ['space_id']);
 14 |             
 15 |             return repository.getSpace(args.space_id);
 16 |         }
 17 |         
 18 |         case 'update_space': {
 19 |             ValidationUtils.validateRequired(args, ['space_id']);
 20 |             
 21 |             await repository.updateSpace(args.space_id, {
 22 |                 name: args.name,
 23 |                 isPrivate: args.isPrivate,
 24 |                 fixedMember: args.fixedMember,
 25 |                 useMultiThread: args.useMultiThread,
 26 |             });
 27 |             return ResponseBuilder.success();
 28 |         }
 29 |         
 30 |         case 'update_space_body': {
 31 |             ValidationUtils.validateRequired(args, ['space_id', 'body']);
 32 |             ValidationUtils.validateString(args.body, 'body');
 33 |             
 34 |             await repository.updateSpaceBody(args.space_id, args.body);
 35 |             return ResponseBuilder.success();
 36 |         }
 37 |         
 38 |         case 'get_space_members': {
 39 |             ValidationUtils.validateRequired(args, ['space_id']);
 40 |             
 41 |             return repository.getSpaceMembers(args.space_id);
 42 |         }
 43 |         
 44 |         case 'update_space_members': {
 45 |             ValidationUtils.validateRequired(args, ['space_id', 'members']);
 46 |             ValidationUtils.validateArray(args.members, 'members');
 47 |             
 48 |             await repository.updateSpaceMembers(args.space_id, args.members);
 49 |             return ResponseBuilder.success();
 50 |         }
 51 |         
 52 |         case 'add_thread': {
 53 |             ValidationUtils.validateRequired(args, ['space_id', 'name']);
 54 |             ValidationUtils.validateString(args.name, 'name');
 55 |             
 56 |             const response = await repository.addThread(args.space_id, args.name);
 57 |             return ResponseBuilder.withId('thread_id', response.id);
 58 |         }
 59 |         
 60 |         case 'update_thread': {
 61 |             ValidationUtils.validateRequired(args, ['thread_id']);
 62 |             
 63 |             await repository.updateThread(args.thread_id, {
 64 |                 name: args.name,
 65 |                 body: args.body,
 66 |             });
 67 |             return ResponseBuilder.success();
 68 |         }
 69 |         
 70 |         case 'add_thread_comment': {
 71 |             ValidationUtils.validateRequired(args, ['space_id', 'thread_id', 'text']);
 72 |             ValidationUtils.validateString(args.text, 'text');
 73 |             
 74 |             const response = await repository.addThreadComment(
 75 |                 args.space_id,
 76 |                 args.thread_id,
 77 |                 {
 78 |                     text: args.text,
 79 |                     mentions: args.mentions || [],
 80 |                 }
 81 |             );
 82 |             return ResponseBuilder.withId('comment_id', response.id);
 83 |         }
 84 |         
 85 |         case 'add_guests': {
 86 |             ValidationUtils.validateRequired(args, ['guests']);
 87 |             ValidationUtils.validateArray(args.guests, 'guests', { minLength: 1 });
 88 |             
 89 |             await repository.addGuests(args.guests);
 90 |             return ResponseBuilder.success();
 91 |         }
 92 |         
 93 |         case 'update_space_guests': {
 94 |             ValidationUtils.validateRequired(args, ['space_id', 'guests']);
 95 |             ValidationUtils.validateArray(args.guests, 'guests');
 96 |             
 97 |             await repository.updateSpaceGuests(args.space_id, args.guests);
 98 |             return ResponseBuilder.success();
 99 |         }
100 |         
101 |         default:
102 |             throw new Error(`Unknown space tool: ${name}`);
103 |     }
104 | }
```

--------------------------------------------------------------------------------
/src/repositories/KintoneSpaceRepository.js:
--------------------------------------------------------------------------------

```javascript
  1 | // src/repositories/KintoneSpaceRepository.js
  2 | import { BaseKintoneRepository } from './base/BaseKintoneRepository.js';
  3 | import { LoggingUtils } from '../utils/LoggingUtils.js';
  4 | import { ResponseBuilder } from '../utils/ResponseBuilder.js';
  5 | 
  6 | export class KintoneSpaceRepository extends BaseKintoneRepository {
  7 |     async getSpace(spaceId) {
  8 |         try {
  9 |             LoggingUtils.logDetailedOperation('getSpace', 'スペース情報取得', { spaceId });
 10 |             const response = await this.client.space.getSpace({ id: spaceId });
 11 |             LoggingUtils.logDetailedOperation('getSpace', 'スペース情報取得完了', { spaceId });
 12 |             return response;
 13 |         } catch (error) {
 14 |             this.handleKintoneError(error, `get space ${spaceId}`);
 15 |         }
 16 |     }
 17 | 
 18 |     async updateSpace(spaceId, settings) {
 19 |         try {
 20 |             LoggingUtils.logDetailedOperation('updateSpace', 'スペース情報更新', { spaceId, settings });
 21 |             await this.client.space.updateSpace({
 22 |                 id: spaceId,
 23 |                 ...settings
 24 |             });
 25 |             LoggingUtils.logDetailedOperation('updateSpace', 'スペース情報更新完了', { spaceId });
 26 |         } catch (error) {
 27 |             this.handleKintoneError(error, `update space ${spaceId}`);
 28 |         }
 29 |     }
 30 | 
 31 |     async updateSpaceBody(spaceId, body) {
 32 |         try {
 33 |             LoggingUtils.logDetailedOperation('updateSpaceBody', 'スペース本文更新', { spaceId, bodyLength: body.length });
 34 |             await this.client.space.updateSpaceBody({
 35 |                 id: spaceId,
 36 |                 body: body
 37 |             });
 38 |             LoggingUtils.logDetailedOperation('updateSpaceBody', 'スペース本文更新完了', { spaceId });
 39 |         } catch (error) {
 40 |             this.handleKintoneError(error, `update space body ${spaceId}`);
 41 |         }
 42 |     }
 43 | 
 44 |     async getSpaceMembers(spaceId) {
 45 |         try {
 46 |             LoggingUtils.logDetailedOperation('getSpaceMembers', 'スペースメンバー取得', { spaceId });
 47 |             const response = await this.client.space.getSpaceMembers({ id: spaceId });
 48 |             LoggingUtils.logDetailedOperation('getSpaceMembers', 'スペースメンバー取得完了', { 
 49 |                 spaceId, 
 50 |                 memberCount: response.members ? response.members.length : 0 
 51 |             });
 52 |             return response;
 53 |         } catch (error) {
 54 |             this.handleKintoneError(error, `get space members ${spaceId}`);
 55 |         }
 56 |     }
 57 | 
 58 |     async updateSpaceMembers(spaceId, members) {
 59 |         try {
 60 |             LoggingUtils.logDetailedOperation('updateSpaceMembers', 'スペースメンバー更新', { 
 61 |                 spaceId, 
 62 |                 memberCount: members.length 
 63 |             });
 64 |             await this.client.space.updateSpaceMembers({
 65 |                 id: spaceId,
 66 |                 members: members
 67 |             });
 68 |             LoggingUtils.logDetailedOperation('updateSpaceMembers', 'スペースメンバー更新完了', { spaceId });
 69 |         } catch (error) {
 70 |             this.handleKintoneError(error, `update space members ${spaceId}`);
 71 |         }
 72 |     }
 73 | 
 74 |     async addThread(spaceId, name) {
 75 |         try {
 76 |             LoggingUtils.logDetailedOperation('addThread', 'スレッド作成', { spaceId, threadName: name });
 77 |             const response = await this.client.space.addThread({
 78 |                 space: spaceId,
 79 |                 name: name
 80 |             });
 81 |             LoggingUtils.logDetailedOperation('addThread', 'スレッド作成完了', { 
 82 |                 spaceId, 
 83 |                 threadId: response.id 
 84 |             });
 85 |             return response;
 86 |         } catch (error) {
 87 |             this.handleKintoneError(error, `add thread to space ${spaceId}`);
 88 |         }
 89 |     }
 90 | 
 91 |     async updateThread(threadId, params) {
 92 |         try {
 93 |             LoggingUtils.logDetailedOperation('updateThread', 'スレッド更新', { threadId, params });
 94 |             await this.client.space.updateThread({
 95 |                 id: threadId,
 96 |                 ...params
 97 |             });
 98 |             LoggingUtils.logDetailedOperation('updateThread', 'スレッド更新完了', { threadId });
 99 |         } catch (error) {
100 |             this.handleKintoneError(error, `update thread ${threadId}`);
101 |         }
102 |     }
103 | 
104 |     async addThreadComment(spaceId, threadId, comment) {
105 |         try {
106 |             LoggingUtils.logDetailedOperation('addThreadComment', 'コメント追加', { 
107 |                 spaceId, 
108 |                 threadId, 
109 |                 commentLength: comment.text ? comment.text.length : 0 
110 |             });
111 |             const response = await this.client.space.addThreadComment({
112 |                 space: spaceId,
113 |                 thread: threadId,
114 |                 comment: comment
115 |             });
116 |             LoggingUtils.logDetailedOperation('addThreadComment', 'コメント追加完了', { 
117 |                 commentId: response.id 
118 |             });
119 |             return response;
120 |         } catch (error) {
121 |             this.handleKintoneError(error, `add comment to thread ${threadId}`);
122 |         }
123 |     }
124 | 
125 |     async updateSpaceGuests(spaceId, guests) {
126 |         try {
127 |             LoggingUtils.logDetailedOperation('updateSpaceGuests', 'スペースゲスト更新', { 
128 |                 spaceId, 
129 |                 guestCount: guests.length 
130 |             });
131 |             await this.client.space.updateSpaceGuests({
132 |                 id: spaceId,
133 |                 guests: guests
134 |             });
135 |             LoggingUtils.logDetailedOperation('updateSpaceGuests', 'スペースゲスト更新完了', { spaceId });
136 |         } catch (error) {
137 |             this.handleKintoneError(error, `update space guests ${spaceId}`);
138 |         }
139 |     }
140 | }
141 | 
```

--------------------------------------------------------------------------------
/src/server/handlers/ToolRouter.js:
--------------------------------------------------------------------------------

```javascript
  1 | import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
  2 | import { handleRecordTools } from '../tools/RecordTools.js';
  3 | import { handleAppTools } from '../tools/AppTools.js';
  4 | import { handleSpaceTools } from '../tools/SpaceTools.js';
  5 | import { handleFieldTools } from '../tools/FieldTools.js';
  6 | import { handleDocumentationTools } from '../tools/DocumentationTools.js';
  7 | import { handleLayoutTools } from '../tools/LayoutTools.js';
  8 | import { handleUserTools } from '../tools/UserTools.js';
  9 | import { handleSystemTools } from '../tools/SystemTools.js';
 10 | import { handleFileTools } from '../tools/FileTools.js';
 11 | import { LoggingUtils } from '../../utils/LoggingUtils.js';
 12 | 
 13 | export class ToolRouter {
 14 |     constructor() {
 15 |         this.toolCategories = {
 16 |             record: {
 17 |                 tools: ['get_record', 'search_records', 'create_record', 'update_record', 'add_record_comment', 
 18 |                        'update_record_status', 'update_record_assignees',
 19 |                        'get_record_comments', 'update_record_comment', 'create_records', 'upsert_record',
 20 |                        'upsert_records'],
 21 |                 handler: handleRecordTools
 22 |             },
 23 |             app: {
 24 |                 tools: [
 25 |                     'create_app', 'deploy_app', 'get_deploy_status', 'update_app_settings', 'get_apps_info',
 26 |                     'get_form_layout', 'get_form_fields', 'update_form_layout', 'get_preview_app_settings',
 27 |                     'get_preview_form_fields', 'get_preview_form_layout',
 28 |                     'move_app_to_space', 'move_app_from_space', 'get_app_actions', 'get_app_plugins',
 29 |                     'get_process_management', 'update_process_management', 'get_views', 'update_views', 'get_app_acl',
 30 |                     'get_field_acl', 'update_field_acl', 'get_reports', 'update_reports',
 31 |                     'get_notifications', 'update_notifications', 'get_per_record_notifications', 'update_per_record_notifications',
 32 |                     'get_reminder_notifications', 'update_reminder_notifications', 'update_app_actions', 'update_plugins',
 33 |                     'get_app_customize', 'update_app_customize', 'update_app_acl', 'get_record_acl', 'evaluate_records_acl'
 34 |                 ],
 35 |                 handler: handleAppTools
 36 |             },
 37 |             space: {
 38 |                 tools: [
 39 |                     'get_space', 'update_space', 'update_space_body', 'get_space_members',
 40 |                     'update_space_members', 'add_thread', 'update_thread', 'add_thread_comment',
 41 |                     'add_guests', 'update_space_guests'
 42 |                 ],
 43 |                 handler: handleSpaceTools
 44 |             },
 45 |             field: {
 46 |                 tools: [
 47 |                     'add_fields', 'update_field', 'create_choice_field', 'create_reference_table_field',
 48 |                     'create_text_field', 'create_number_field', 'create_date_field', 'create_time_field',
 49 |                     'create_datetime_field', 'create_rich_text_field', 'create_attachment_field',
 50 |                     'create_user_select_field', 'create_subtable_field', 'create_calc_field',
 51 |                     'create_status_field', 'create_related_records_field', 'create_link_field'
 52 |                 ],
 53 |                 handler: handleFieldTools
 54 |             },
 55 |             documentation: {
 56 |                 tools: [
 57 |                     'get_field_type_documentation', 'get_available_field_types',
 58 |                     'get_documentation_tool_description', 'get_field_creation_tool_description',
 59 |                     'get_group_element_structure', 'get_query_language_documentation'
 60 |                 ],
 61 |                 handler: handleDocumentationTools
 62 |             },
 63 |             file: {
 64 |                 tools: ['upload_file', 'download_file'],
 65 |                 handler: handleFileTools
 66 |             },
 67 |             layout: {
 68 |                 tools: [
 69 |                     'create_form_layout', 'update_form_layout', 'add_layout_element',
 70 |                     'create_group_layout', 'create_table_layout'
 71 |                 ],
 72 |                 handler: handleLayoutTools
 73 |             },
 74 |             user: {
 75 |                 tools: ['get_users', 'get_groups', 'get_group_users'],
 76 |                 handler: handleUserTools
 77 |             },
 78 |             system: {
 79 |                 tools: ['get_kintone_domain', 'get_kintone_username'],
 80 |                 handler: handleSystemTools
 81 |             }
 82 |         };
 83 |     }
 84 | 
 85 |     findToolCategory(toolName) {
 86 |         for (const [categoryName, category] of Object.entries(this.toolCategories)) {
 87 |             if (category.tools.includes(toolName)) {
 88 |                 return { categoryName, handler: category.handler };
 89 |             }
 90 |         }
 91 |         return null;
 92 |     }
 93 | 
 94 |     async routeToolRequest(toolName, args, repository) {
 95 |         const toolCategory = this.findToolCategory(toolName);
 96 |         
 97 |         if (!toolCategory) {
 98 |             throw new McpError(
 99 |                 ErrorCode.MethodNotFound,
100 |                 `Unknown tool: ${toolName}`
101 |             );
102 |         }
103 | 
104 |         return await toolCategory.handler(toolName, args, repository);
105 |     }
106 | 
107 |     handleLookupFieldSpecialCase(toolName, args, repository) {
108 |         if (toolName !== 'create_lookup_field') {
109 |             return null;
110 |         }
111 | 
112 |         return this.createLookupFieldResponse(args, repository);
113 |     }
114 | 
115 |     async createLookupFieldResponse(args, repository) {
116 |         const fieldConfig = await handleFieldTools('create_lookup_field', args, repository);
117 | 
118 |         const note = `注意: create_lookup_field ツールは設定オブジェクトを生成するだけのヘルパーツールです。実際にフィールドを追加するには、この結果を add_fields ツールに渡してください。`;
119 |         const lookupNote = `
120 | 【重要】ルックアップフィールドについて
121 | - ルックアップフィールドは基本的なフィールドタイプ(SINGLE_LINE_TEXT、NUMBERなど)に、lookup属性を追加したものです
122 | - フィールドタイプとして "LOOKUP" を指定するのではなく、適切な基本タイプを指定し、その中にlookupプロパティを設定します
123 | - 参照先アプリは運用環境にデプロイされている必要があります
124 | - ルックアップのキーフィールド自体はフィールドマッピングに含めないでください
125 | - lookupPickerFieldsとsortは省略可能ですが、指定することを強く推奨します
126 | `;
127 |         const example = `使用例:
128 | add_fields({
129 |   app_id: アプリID,
130 |   properties: {
131 |     "${fieldConfig.code}": ${JSON.stringify(fieldConfig, null, 2)}
132 |   }
133 | });`;
134 |         LoggingUtils.info('field', 'lookup_field_guidance_generated', { fieldCode: fieldConfig.code });
135 |         
136 |         const result = {
137 |             ...fieldConfig,
138 |             _note: note,
139 |             _lookupNote: lookupNote,
140 |             _example: example
141 |         };
142 |         
143 |         return {
144 |             content: [
145 |                 {
146 |                     type: 'text',
147 |                     text: JSON.stringify(result, null, 2)
148 |                 },
149 |                 {
150 |                     type: 'text',
151 |                     text: note
152 |                 },
153 |                 {
154 |                     type: 'text',
155 |                     text: lookupNote
156 |                 },
157 |                 {
158 |                     type: 'text',
159 |                     text: example
160 |                 }
161 |             ]
162 |         };
163 |     }
164 | }
165 | 
```

--------------------------------------------------------------------------------
/src/repositories/base/http/KintoneHttpClient.js:
--------------------------------------------------------------------------------

```javascript
  1 | // src/repositories/base/http/KintoneHttpClient.js
  2 | import { KintoneApiError } from './KintoneApiError.js';
  3 | 
  4 | const DEFAULT_TIMEOUT_MS = 60000;
  5 | 
  6 | function buildPath({ endpointName, guestSpaceId, preview = false }) {
  7 |     const guestPath = guestSpaceId !== undefined && guestSpaceId !== null
  8 |         ? `/guest/${guestSpaceId}`
  9 |         : '';
 10 |     const previewPath = preview ? '/preview' : '';
 11 |     return `/k${guestPath}/v1${previewPath}/${endpointName}.json`;
 12 | }
 13 | 
 14 | const FORM_DATA_SUPPORTED = typeof FormData !== 'undefined';
 15 | 
 16 | function isFormData(value) {
 17 |     return FORM_DATA_SUPPORTED && value instanceof FormData;
 18 | }
 19 | 
 20 | function stripUndefined(value) {
 21 |     if (value === null || value === undefined) {
 22 |         return undefined;
 23 |     }
 24 |     if (Array.isArray(value)) {
 25 |         return value.map(stripUndefined);
 26 |     }
 27 |     if (typeof value === 'object' && !isFormData(value) && !(value instanceof Date) && !(value instanceof Buffer)) {
 28 |         const result = {};
 29 |         for (const [key, entry] of Object.entries(value)) {
 30 |             if (entry !== undefined) {
 31 |                 const cleaned = stripUndefined(entry);
 32 |                 if (cleaned !== undefined) {
 33 |                     result[key] = cleaned;
 34 |                 }
 35 |             }
 36 |         }
 37 |         return result;
 38 |     }
 39 |     return value;
 40 | }
 41 | 
 42 | export class KintoneHttpClient {
 43 |     constructor(credentials) {
 44 |         this.credentials = credentials;
 45 |         this.baseUrl = `https://${credentials.domain}`;
 46 |         this.authHeader = Buffer.from(`${credentials.username}:${credentials.password}`).toString('base64');
 47 |     }
 48 | 
 49 |     getDefaultHeaders() {
 50 |         return {
 51 |             'X-Cybozu-Authorization': this.authHeader,
 52 |             'X-Requested-With': 'XMLHttpRequest',
 53 |             'Accept': 'application/json'
 54 |         };
 55 |     }
 56 | 
 57 |     async request(method, endpointName, { params, data, preview = false, headers = {}, responseType, rawResponse = false } = {}) {
 58 |         const path = buildPath({
 59 |             endpointName,
 60 |             guestSpaceId: this.credentials.guestSpaceId,
 61 |             preview
 62 |         });
 63 |         const url = new URL(path, this.baseUrl);
 64 | 
 65 |         const cleanedParams = params ? stripUndefined(params) : undefined;
 66 |         if (cleanedParams) {
 67 |             for (const [key, value] of Object.entries(cleanedParams)) {
 68 |                 if (value === undefined || value === null) {
 69 |                     continue;
 70 |                 }
 71 |                 if (Array.isArray(value)) {
 72 |                     for (const item of value) {
 73 |                         if (item !== undefined && item !== null) {
 74 |                             url.searchParams.append(key, String(item));
 75 |                         }
 76 |                     }
 77 |                 } else {
 78 |                     url.searchParams.append(key, String(value));
 79 |                 }
 80 |             }
 81 |         }
 82 | 
 83 |         const requestHeaders = new Headers(this.getDefaultHeaders());
 84 |         for (const [key, value] of Object.entries(headers)) {
 85 |             if (value !== undefined && value !== null) {
 86 |                 requestHeaders.set(key, String(value));
 87 |             }
 88 |         }
 89 | 
 90 |         const init = {
 91 |             method: method.toUpperCase(),
 92 |             headers: requestHeaders,
 93 |             signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
 94 |         };
 95 | 
 96 |         const expectedResponseType = responseType || 'json';
 97 | 
 98 |         if (data !== undefined) {
 99 |             if (isFormData(data)) {
100 |                 // fetchがboundary付きヘッダーを自動付与するため、Content-Typeは削除
101 |                 requestHeaders.delete('Content-Type');
102 |                 init.body = data;
103 |             } else {
104 |                 const payload = stripUndefined(data);
105 |                 init.body = JSON.stringify(payload);
106 |                 if (!requestHeaders.has('Content-Type')) {
107 |                     requestHeaders.set('Content-Type', 'application/json');
108 |                 }
109 |             }
110 |         }
111 | 
112 |         let response;
113 |         try {
114 |             response = await fetch(url, init);
115 |         } catch (error) {
116 |             if (error instanceof KintoneApiError) {
117 |                 throw error;
118 |             }
119 |             const isAbortError = error?.name === 'AbortError';
120 |             throw new KintoneApiError(
121 |                 isAbortError ? 'kintone API request timed out' : 'kintone API request failed without response',
122 |                 {
123 |                     status: null,
124 |                     responseBody: null
125 |                 }
126 |             );
127 |         }
128 | 
129 |         const parseErrorBody = async () => {
130 |             const contentType = response.headers.get('content-type') || '';
131 |             if (contentType.includes('application/json')) {
132 |                 try {
133 |                     return await response.json();
134 |                 } catch (parseError) {
135 |                     return null;
136 |                 }
137 |             }
138 |             try {
139 |                 return await response.text();
140 |             } catch (parseError) {
141 |                 return null;
142 |             }
143 |         };
144 | 
145 |         if (!response.ok) {
146 |             const body = await parseErrorBody();
147 |             const message = typeof body === 'object' && body?.message
148 |                 ? body.message
149 |                 : `kintone API request failed with status ${response.status}`;
150 | 
151 |             throw new KintoneApiError(message, {
152 |                 status: response.status,
153 |                 code: typeof body === 'object' ? body?.code : undefined,
154 |                 errors: typeof body === 'object' ? body?.errors : undefined,
155 |                 responseBody: body
156 |             });
157 |         }
158 | 
159 |         if (rawResponse) {
160 |             const dataBuffer = expectedResponseType === 'arraybuffer'
161 |                 ? Buffer.from(await response.arrayBuffer())
162 |                 : await response.text();
163 |             return {
164 |                 data: dataBuffer,
165 |                 headers: Object.fromEntries(response.headers.entries()),
166 |                 status: response.status
167 |             };
168 |         }
169 | 
170 |         if (expectedResponseType === 'arraybuffer') {
171 |             return Buffer.from(await response.arrayBuffer());
172 |         }
173 | 
174 |         if (expectedResponseType === 'text') {
175 |             return await response.text();
176 |         }
177 | 
178 |         if (response.status === 204) {
179 |             return null;
180 |         }
181 | 
182 |         const text = await response.text();
183 |         if (!text) {
184 |             return {};
185 |         }
186 | 
187 |         try {
188 |             return JSON.parse(text);
189 |         } catch (error) {
190 |             throw new KintoneApiError('kintone API returned invalid JSON response', {
191 |                 status: response.status,
192 |                 responseBody: text
193 |             });
194 |         }
195 |     }
196 | 
197 |     get(endpointName, params, options = {}) {
198 |         return this.request('get', endpointName, { ...options, params });
199 |     }
200 | 
201 |     post(endpointName, data, options = {}) {
202 |         return this.request('post', endpointName, { ...options, data });
203 |     }
204 | 
205 |     put(endpointName, data, options = {}) {
206 |         return this.request('put', endpointName, { ...options, data });
207 |     }
208 | 
209 |     delete(endpointName, data, options = {}) {
210 |         return this.request('delete', endpointName, { ...options, data });
211 |     }
212 | }
213 | 
```

--------------------------------------------------------------------------------
/src/repositories/KintoneRecordRepository.js:
--------------------------------------------------------------------------------

```javascript
  1 | // src/repositories/KintoneRecordRepository.js
  2 | import { BaseKintoneRepository } from './base/BaseKintoneRepository.js';
  3 | import { KintoneRecord } from '../models/KintoneRecord.js';
  4 | import { LoggingUtils } from '../utils/LoggingUtils.js';
  5 | 
  6 | export class KintoneRecordRepository extends BaseKintoneRepository {
  7 |     async getRecord(appId, recordId) {
  8 |         const params = { app: appId, id: recordId };
  9 |         return this.executeWithDetailedLogging(
 10 |             'getRecord',
 11 |             params,
 12 |             () => this.client.record.getRecord(params),
 13 |             `get record ${appId}/${recordId}`
 14 |         ).then(response => new KintoneRecord(appId, recordId, response.record));
 15 |     }
 16 | 
 17 |     async searchRecords(appId, query, fields = []) {
 18 |         const params = { app: appId };
 19 | 
 20 |         // クエリ文字列の処理
 21 |         if (query) {
 22 |             // クエリ文字列が order や limit のみで構成されているかチェック
 23 |             // 条件句の検出: 比較/文字列/集合/空判定(is empty / is not empty)をサポート
 24 |             const hasCondition = /[^\s]+(?:\s*(?:=|!=|>|<|>=|<=|like|in|not\s+in)|\s+is\s+(?:not\s+)?empty)/i.test(query);
 25 |             const hasOrderOrLimit = /(order |limit )/i.test(query);
 26 | 
 27 |             // order や limit のみの場合、$id > 0 を先頭に挿入
 28 |             if (!hasCondition && hasOrderOrLimit) {
 29 |                 params.query = `$id > 0 ${query}`;
 30 |                 LoggingUtils.logOperation('Modified query', params.query);
 31 |             } else {
 32 |                 params.query = query;
 33 |             }
 34 |         }
 35 | 
 36 |         if (fields.length > 0) {
 37 |             params.fields = fields;
 38 |         }
 39 | 
 40 |         // getAllRecords ではなく getRecords を使用
 41 |         return this.executeWithDetailedLogging(
 42 |             'searchRecords',
 43 |             params,
 44 |             () => this.client.record.getRecords(params),
 45 |             `search records ${appId}`
 46 |         ).then(response => {
 47 |             const records = response.records || [];
 48 |             LoggingUtils.logOperation(`Found records`, `${records.length} records`);
 49 |             return records.map((record) => {
 50 |                 const recordId = record.$id?.value || 'unknown';
 51 |                 return new KintoneRecord(appId, recordId, record);
 52 |             });
 53 |         });
 54 |     }
 55 | 
 56 |     async createRecord(appId, fields) {
 57 |         const params = { app: appId, record: fields };
 58 |         return this.executeWithDetailedLogging(
 59 |             'createRecord',
 60 |             params,
 61 |             () => this.client.record.addRecord(params),
 62 |             `create record in app ${appId}`
 63 |         ).then(response => response.id);
 64 |     }
 65 | 
 66 |     async updateRecord(record) {
 67 |         const params = {
 68 |             app: record.appId,
 69 |             id: record.recordId,
 70 |             record: record.fields
 71 |         };
 72 |         return this.executeWithDetailedLogging(
 73 |             'updateRecord',
 74 |             params,
 75 |             () => this.client.record.updateRecord(params),
 76 |             `update record ${record.appId}/${record.recordId}`
 77 |         );
 78 |     }
 79 | 
 80 |     async updateRecordByKey(appId, keyField, keyValue, fields) {
 81 |         const params = {
 82 |             app: appId,
 83 |             updateKey: {
 84 |                 field: keyField,
 85 |                 value: keyValue
 86 |             },
 87 |             record: fields
 88 |         };
 89 |         return this.executeWithDetailedLogging(
 90 |             'updateRecordByKey',
 91 |             params,
 92 |             () => this.client.record.updateRecordByUpdateKey(params),
 93 |             `update record by key ${appId}/${keyField}=${keyValue}`
 94 |         );
 95 |     }
 96 | 
 97 |     async addRecordComment(appId, recordId, text, mentions = []) {
 98 |         const params = {
 99 |             app: appId,
100 |             record: recordId,
101 |             comment: {
102 |                 text: text,
103 |                 mentions: mentions
104 |             }
105 |         };
106 |         return this.executeWithDetailedLogging(
107 |             'addRecordComment',
108 |             params,
109 |             () => this.client.record.addRecordComment(params),
110 |             `add comment to record ${appId}/${recordId}`
111 |         ).then(response => response.id);
112 |     }
113 | 
114 |     async getRecordAcl(appId, recordIds) {
115 |         // kintone REST APIではレコード単位のアクセス権限取得はサポートされていません
116 |         throw new Error(
117 |             'レコード単位のアクセス権限取得機能はkintone REST APIではサポートされていません。\n\n' +
118 |             '代替案:\n' +
119 |             '1. アプリ全体のアクセス権限を確認する場合は get_app_acl を使用してください\n' +
120 |             '2. プロセス管理のステータスに基づくアクセス制御を確認する場合は get_process_management を使用してください\n' +
121 |             '3. レコードの作成者・更新者を確認する場合は、レコード取得時に CREATOR/MODIFIER フィールドを参照してください'
122 |         );
123 |     }
124 | 
125 |     async updateRecordStatus(appId, recordId, action, assignee = null) {
126 |         const params = {
127 |             app: appId,
128 |             id: recordId,
129 |             action: action
130 |         };
131 |         
132 |         if (assignee) {
133 |             params.assignee = assignee;
134 |         }
135 |         
136 |         return this.executeWithDetailedLogging(
137 |             'updateRecordStatus',
138 |             params,
139 |             () => this.client.record.updateRecordStatus(params),
140 |             `update record status ${appId}/${recordId}`
141 |         );
142 |     }
143 | 
144 |     async updateRecordAssignees(appId, recordId, assignees) {
145 |         const params = {
146 |             app: appId,
147 |             id: recordId,
148 |             assignees: assignees
149 |         };
150 |         return this.executeWithDetailedLogging(
151 |             'updateRecordAssignees',
152 |             params,
153 |             () => this.client.record.updateRecordAssignees(params),
154 |             `update record assignees ${appId}/${recordId}`
155 |         );
156 |     }
157 | 
158 |     async getRecordComments(appId, recordId, order = 'desc', offset = 0, limit = 10) {
159 |         const params = {
160 |             app: appId,
161 |             record: recordId,
162 |             order: order,
163 |             offset: offset,
164 |             limit: limit
165 |         };
166 |         return this.executeWithDetailedLogging(
167 |             'getRecordComments',
168 |             params,
169 |             () => this.client.record.getRecordComments(params),
170 |             `get comments for record ${appId}/${recordId}`
171 |         ).then(response => ({
172 |             comments: response.comments,
173 |             totalCount: response.totalCount
174 |         }));
175 |     }
176 | 
177 |     async updateRecordComment(appId, recordId, commentId, text, mentions = []) {
178 |         const params = {
179 |             app: appId,
180 |             record: recordId,
181 |             comment: commentId,
182 |             text: text,
183 |             mentions: mentions
184 |         };
185 |         return this.executeWithDetailedLogging(
186 |             'updateRecordComment',
187 |             params,
188 |             () => this.client.record.updateRecordComment(params),
189 |             `update comment ${commentId} for record ${appId}/${recordId}`
190 |         );
191 |     }
192 | 
193 |     async createRecords(appId, records) {
194 |         const params = {
195 |             app: appId,
196 |             records: records
197 |         };
198 |         return this.executeWithDetailedLogging(
199 |             'createRecords',
200 |             params,
201 |             () => this.client.record.addRecords(params),
202 |             `create records in app ${appId}`
203 |         );
204 |     }
205 | 
206 |     async updateRecords(appId, records) {
207 |         const params = {
208 |             app: appId,
209 |             records: records
210 |         };
211 |         return this.executeWithDetailedLogging(
212 |             'updateRecords',
213 |             params,
214 |             () => this.client.record.updateRecords(params),
215 |             `update records in app ${appId}`
216 |         );
217 |     }
218 | 
219 |     async upsertRecord(appId, updateKey, fields) {
220 |         const params = {
221 |             app: appId,
222 |             updateKey: updateKey,
223 |             record: fields
224 |         };
225 |         return this.executeWithDetailedLogging(
226 |             'upsertRecord',
227 |             params,
228 |             () => this.client.record.upsertRecord(params),
229 |             `upsert record in app ${appId} with key ${updateKey.field}=${updateKey.value}`
230 |         );
231 |     }
232 | 
233 |     async upsertRecords(appId, records) {
234 |         const params = {
235 |             app: appId,
236 |             records: records.map((entry) => ({
237 |                 updateKey: entry.updateKey,
238 |                 record: entry.fields
239 |             }))
240 |         };
241 | 
242 |         return this.executeWithDetailedLogging(
243 |             'upsertRecords',
244 |             params,
245 |             () => this.client.record.upsertRecords(params),
246 |             `upsert multiple records in app ${appId}`
247 |         );
248 |     }
249 | }
250 | 
```

--------------------------------------------------------------------------------
/src/server/tools/RecordTools.js:
--------------------------------------------------------------------------------

```javascript
  1 | // src/server/tools/RecordTools.js
  2 | import { KintoneRecord } from '../../models/KintoneRecord.js';
  3 | import { ValidationUtils } from '../../utils/ValidationUtils.js';
  4 | import { LoggingUtils } from '../../utils/LoggingUtils.js';
  5 | import { ResponseBuilder } from '../../utils/ResponseBuilder.js';
  6 | 
  7 | // レコード関連のツールを処理する関数
  8 | export async function handleRecordTools(name, args, repository) {
  9 |     // 共通のツール実行ログ
 10 |     LoggingUtils.logToolExecution('record', name, args);
 11 |     
 12 |     switch (name) {
 13 |         case 'get_record': {
 14 |             ValidationUtils.validateRequired(args, ['app_id', 'record_id']);
 15 |             
 16 |             const record = await repository.getRecord(args.app_id, args.record_id);
 17 |             return record.fields;  // KintoneRecord ではなく fields を返す
 18 |         }
 19 |         
 20 |         case 'search_records': {
 21 |             ValidationUtils.validateRequired(args, ['app_id']);
 22 |             
 23 |             // クエリーが提供されている場合、kintoneクエリー構文を検証
 24 |             if (args.query) {
 25 |                 ValidationUtils.validateKintoneQuery(args.query);
 26 |             }
 27 |             
 28 |             const records = await repository.searchRecords(
 29 |                 args.app_id,
 30 |                 args.query,
 31 |                 args.fields
 32 |             );
 33 |             return records.map(r => r.fields);
 34 |         }
 35 |         
 36 |         case 'create_record': {
 37 |             ValidationUtils.validateRequired(args, ['app_id', 'fields']);
 38 |             ValidationUtils.validateObject(args.fields, 'fields');
 39 |             
 40 |             // フィールドの検証
 41 |             if (!args.fields.project_manager) {
 42 |                 LoggingUtils.logWarning('create_record', 'project_manager field is missing');
 43 |             }
 44 |             
 45 |             const recordId = await repository.createRecord(
 46 |                 args.app_id,
 47 |                 args.fields
 48 |             );
 49 |             return ResponseBuilder.recordCreated(recordId);
 50 |         }
 51 |         
 52 |         case 'update_record': {
 53 |             ValidationUtils.validateRequired(args, ['app_id', 'fields']);
 54 |             
 55 |             // レコードIDまたはupdateKeyのいずれかが必要
 56 |             if (!args.record_id && !args.updateKey) {
 57 |                 throw new Error('record_id または updateKey は必須パラメータです。');
 58 |             }
 59 |             
 60 |             ValidationUtils.validateObject(args.fields, 'fields');
 61 |             
 62 |             let response;
 63 |             if (args.record_id) {
 64 |                 // レコードIDを使用した更新
 65 |                 response = await repository.updateRecord(
 66 |                     new KintoneRecord(
 67 |                         args.app_id,
 68 |                         args.record_id,
 69 |                         args.fields
 70 |                     )
 71 |                 );
 72 |             } else {
 73 |                 // updateKeyを使用した更新
 74 |                 response = await repository.updateRecordByKey(
 75 |                     args.app_id,
 76 |                     args.updateKey.field,
 77 |                     args.updateKey.value,
 78 |                     args.fields
 79 |                 );
 80 |             }
 81 |             
 82 |             return ResponseBuilder.recordUpdated(response.revision);
 83 |         }
 84 |         
 85 |         case 'add_record_comment': {
 86 |             ValidationUtils.validateRequired(args, ['app_id', 'record_id', 'text']);
 87 |             ValidationUtils.validateString(args.text, 'text');
 88 |             
 89 |             const commentId = await repository.addRecordComment(
 90 |                 args.app_id,
 91 |                 args.record_id,
 92 |                 args.text,
 93 |                 args.mentions || []
 94 |             );
 95 |             return ResponseBuilder.withId('comment_id', commentId);
 96 |         }
 97 |         
 98 |         case 'update_record_status': {
 99 |             ValidationUtils.validateRequired(args, ['app_id', 'record_id', 'action']);
100 |             ValidationUtils.validateString(args.action, 'action');
101 |             
102 |             const response = await repository.updateRecordStatus(
103 |                 args.app_id,
104 |                 args.record_id,
105 |                 args.action,
106 |                 args.assignee
107 |             );
108 |             return ResponseBuilder.withRevision(response.revision);
109 |         }
110 |         
111 |         case 'update_record_assignees': {
112 |             ValidationUtils.validateRequired(args, ['app_id', 'record_id', 'assignees']);
113 |             ValidationUtils.validateArray(args.assignees, 'assignees', {
114 |                 maxLength: 100
115 |             });
116 |             
117 |             const response = await repository.updateRecordAssignees(
118 |                 args.app_id,
119 |                 args.record_id,
120 |                 args.assignees
121 |             );
122 |             return ResponseBuilder.withRevision(response.revision);
123 |         }
124 |         
125 |         case 'get_record_comments': {
126 |             ValidationUtils.validateRequired(args, ['app_id', 'record_id']);
127 |             
128 |             const comments = await repository.getRecordComments(
129 |                 args.app_id,
130 |                 args.record_id,
131 |                 args.order || 'desc',
132 |                 args.offset || 0,
133 |                 args.limit || 10
134 |             );
135 |             return comments;
136 |         }
137 |         
138 |         case 'update_record_comment': {
139 |             ValidationUtils.validateRequired(args, ['app_id', 'record_id', 'comment_id', 'text']);
140 |             ValidationUtils.validateString(args.text, 'text');
141 |             
142 |             await repository.updateRecordComment(
143 |                 args.app_id,
144 |                 args.record_id,
145 |                 args.comment_id,
146 |                 args.text,
147 |                 args.mentions || []
148 |             );
149 |             return ResponseBuilder.success();
150 |         }
151 |         
152 |         case 'create_records': {
153 |             ValidationUtils.validateRequired(args, ['app_id', 'records']);
154 |             ValidationUtils.validateArray(args.records, 'records', {
155 |                 minLength: 1,
156 |                 maxLength: 100
157 |             });
158 |             
159 |             const result = await repository.createRecords(args.app_id, args.records);
160 |             return ResponseBuilder.recordsCreated(result.ids, result.revisions);
161 |         }
162 |         
163 |         case 'upsert_record': {
164 |             ValidationUtils.validateRequired(args, ['app_id', 'updateKey', 'fields']);
165 |             ValidationUtils.validateObject(args.updateKey, 'updateKey');
166 |             ValidationUtils.validateString(args.updateKey.field, 'updateKey.field');
167 |             ValidationUtils.validateObject(args.fields, 'fields');
168 |             
169 |             const result = await repository.upsertRecord(
170 |                 args.app_id,
171 |                 args.updateKey,
172 |                 args.fields
173 |             );
174 |             
175 |             // result.id: 作成または更新されたレコードのID
176 |             // result.revision: レコードのリビジョン番号
177 |             return {
178 |                 record_id: result.id,
179 |                 revision: result.revision,
180 |                 message: `レコードをupsertしました (ID: ${result.id})`
181 |             };
182 |         }
183 | 
184 |         case 'upsert_records': {
185 |             ValidationUtils.validateRequired(args, ['app_id', 'records']);
186 |             ValidationUtils.validateArray(args.records, 'records', {
187 |                 minLength: 1,
188 |                 maxLength: 100
189 |             });
190 | 
191 |             const normalizedRecords = args.records.map((entry, index) => {
192 |                 ValidationUtils.validateObject(entry, `records[${index}]`);
193 |                 ValidationUtils.validateObject(entry.updateKey, `records[${index}].updateKey`);
194 |                 ValidationUtils.validateString(entry.updateKey.field, `records[${index}].updateKey.field`);
195 |                 if (entry.updateKey.value === undefined || entry.updateKey.value === null) {
196 |                     throw new Error(`records[${index}].updateKey.value は必須です。`);
197 |                 }
198 |                 ValidationUtils.validateObject(entry.fields, `records[${index}].fields`);
199 | 
200 |                 return {
201 |                     updateKey: {
202 |                         field: entry.updateKey.field,
203 |                         value: entry.updateKey.value
204 |                     },
205 |                     fields: entry.fields
206 |                 };
207 |             });
208 | 
209 |             const result = await repository.upsertRecords(args.app_id, normalizedRecords);
210 |             return ResponseBuilder.recordsUpserted(result, {
211 |                 message: `${result.length}件のレコードをupsertしました`
212 |             });
213 |         }
214 | 
215 |         default:
216 |             throw new Error(`Unknown record tool: ${name}`);
217 |     }
218 | }
219 | 
```

--------------------------------------------------------------------------------
/src/repositories/KintoneRepository.js:
--------------------------------------------------------------------------------

```javascript
  1 | // src/repositories/KintoneRepository.js
  2 | import { KintoneRecordRepository } from './KintoneRecordRepository.js';
  3 | import { KintoneAppRepository } from './KintoneAppRepository.js';
  4 | import { KintoneFileRepository } from './KintoneFileRepository.js';
  5 | import { KintoneSpaceRepository } from './KintoneSpaceRepository.js';
  6 | import { KintoneUserRepository } from './KintoneUserRepository.js';
  7 | 
  8 | export class KintoneRepository {
  9 |     constructor(credentials) {
 10 |         this.credentials = credentials;
 11 |         
 12 |         // 各専門リポジトリのインスタンスを作成
 13 |         this.recordRepo = new KintoneRecordRepository(credentials);
 14 |         this.appRepo = new KintoneAppRepository(credentials);
 15 |         this.fileRepo = new KintoneFileRepository(credentials);
 16 |         this.spaceRepo = new KintoneSpaceRepository(credentials);
 17 |         this.userRepo = new KintoneUserRepository(credentials);
 18 |     }
 19 | 
 20 |     // プレビュー環境のアプリ設定を取得
 21 |     async getPreviewAppSettings(appId, lang) {
 22 |         return this.appRepo.getPreviewAppSettings(appId, lang);
 23 |     }
 24 | 
 25 |     // プレビュー環境のフォームフィールド情報を取得
 26 |     async getPreviewFormFields(appId, lang) {
 27 |         return this.appRepo.getPreviewFormFields(appId, lang);
 28 |     }
 29 | 
 30 |     // プレビュー環境のフォームレイアウト情報を取得
 31 |     async getPreviewFormLayout(appId) {
 32 |         return this.appRepo.getPreviewFormLayout(appId);
 33 |     }
 34 | 
 35 |     // レコード関連
 36 |     async getRecord(appId, recordId) {
 37 |         return this.recordRepo.getRecord(appId, recordId);
 38 |     }
 39 | 
 40 |     async searchRecords(appId, query, fields = []) {
 41 |         return this.recordRepo.searchRecords(appId, query, fields);
 42 |     }
 43 | 
 44 |     async createRecord(appId, fields) {
 45 |         return this.recordRepo.createRecord(appId, fields);
 46 |     }
 47 | 
 48 |     async updateRecord(record) {
 49 |         return this.recordRepo.updateRecord(record);
 50 |     }
 51 | 
 52 |     async addRecordComment(appId, recordId, text, mentions = []) {
 53 |         return this.recordRepo.addRecordComment(appId, recordId, text, mentions);
 54 |     }
 55 | 
 56 |     async getRecordAcl(appId, recordId) {
 57 |         return this.appRepo.getRecordAcl(appId, recordId);
 58 |     }
 59 | 
 60 |     async evaluateRecordsAcl(appId, recordIds) {
 61 |         return this.appRepo.evaluateRecordsAcl(appId, recordIds);
 62 |     }
 63 | 
 64 |     async updateRecordStatus(appId, recordId, action, assignee = null) {
 65 |         return this.recordRepo.updateRecordStatus(appId, recordId, action, assignee);
 66 |     }
 67 | 
 68 |     async updateRecordAssignees(appId, recordId, assignees) {
 69 |         return this.recordRepo.updateRecordAssignees(appId, recordId, assignees);
 70 |     }
 71 | 
 72 |     async getRecordComments(appId, recordId, order = 'desc', offset = 0, limit = 10) {
 73 |         return this.recordRepo.getRecordComments(appId, recordId, order, offset, limit);
 74 |     }
 75 | 
 76 |     async updateRecordComment(appId, recordId, commentId, text, mentions = []) {
 77 |         return this.recordRepo.updateRecordComment(appId, recordId, commentId, text, mentions);
 78 |     }
 79 | 
 80 |     async createRecords(appId, records) {
 81 |         return this.recordRepo.createRecords(appId, records);
 82 |     }
 83 | 
 84 |     async updateRecords(appId, records) {
 85 |         return this.recordRepo.updateRecords(appId, records);
 86 |     }
 87 | 
 88 |     async upsertRecord(appId, updateKey, fields) {
 89 |         return this.recordRepo.upsertRecord(appId, updateKey, fields);
 90 |     }
 91 | 
 92 |     async upsertRecords(appId, records) {
 93 |         return this.recordRepo.upsertRecords(appId, records);
 94 |     }
 95 | 
 96 |     async updateRecordByKey(appId, keyField, keyValue, fields) {
 97 |         return this.recordRepo.updateRecordByKey(appId, keyField, keyValue, fields);
 98 |     }
 99 | 
100 |     // アプリ関連
101 |     async getAppsInfo(params) {
102 |         return this.appRepo.getAppsInfo(params);
103 |     }
104 | 
105 |     async createApp(name, space = null, thread = null) {
106 |         return this.appRepo.createApp(name, space, thread);
107 |     }
108 | 
109 |     async addFields(appId, properties) {
110 |         return this.appRepo.addFields(appId, properties);
111 |     }
112 | 
113 |     async deployApp(apps) {
114 |         return this.appRepo.deployApp(apps);
115 |     }
116 | 
117 |     async getDeployStatus(apps) {
118 |         return this.appRepo.getDeployStatus(apps);
119 |     }
120 | 
121 |     async updateAppSettings(appId, settings) {
122 |         return this.appRepo.updateAppSettings(appId, settings);
123 |     }
124 | 
125 |     async getFormLayout(appId) {
126 |         return this.appRepo.getFormLayout(appId);
127 |     }
128 | 
129 |     async getFormFields(appId) {
130 |         return this.appRepo.getFormFields(appId);
131 |     }
132 | 
133 |     async updateFormLayout(appId, layout, revision = -1) {
134 |         return this.appRepo.updateFormLayout(appId, layout, revision);
135 |     }
136 | 
137 |     async updateFormFields(appId, properties, revision = -1) {
138 |         return this.appRepo.updateFormFields(appId, properties, revision);
139 |     }
140 | 
141 |     async deleteFormFields(appId, fields, revision = -1) {
142 |         return this.appRepo.deleteFormFields(appId, fields, revision);
143 |     }
144 | 
145 |     // ファイル関連
146 |     async uploadFile(fileName, fileData) {
147 |         return this.fileRepo.uploadFile(fileName, fileData);
148 |     }
149 | 
150 |     async downloadFile(fileKey) {
151 |         return this.fileRepo.downloadFile(fileKey);
152 |     }
153 | 
154 |     // スペース関連
155 |     async getSpace(spaceId) {
156 |         return this.spaceRepo.getSpace(spaceId);
157 |     }
158 | 
159 |     async updateSpace(spaceId, settings) {
160 |         return this.spaceRepo.updateSpace(spaceId, settings);
161 |     }
162 | 
163 |     async updateSpaceBody(spaceId, body) {
164 |         return this.spaceRepo.updateSpaceBody(spaceId, body);
165 |     }
166 | 
167 |     async getSpaceMembers(spaceId) {
168 |         return this.spaceRepo.getSpaceMembers(spaceId);
169 |     }
170 | 
171 |     async updateSpaceMembers(spaceId, members) {
172 |         return this.spaceRepo.updateSpaceMembers(spaceId, members);
173 |     }
174 | 
175 |     async addThread(spaceId, name) {
176 |         return this.spaceRepo.addThread(spaceId, name);
177 |     }
178 | 
179 |     async updateThread(threadId, params) {
180 |         return this.spaceRepo.updateThread(threadId, params);
181 |     }
182 | 
183 |     async addThreadComment(spaceId, threadId, comment) {
184 |         return this.spaceRepo.addThreadComment(spaceId, threadId, comment);
185 |     }
186 | 
187 |     async updateSpaceGuests(spaceId, guests) {
188 |         return this.spaceRepo.updateSpaceGuests(spaceId, guests);
189 |     }
190 | 
191 |     // アプリをスペースに移動させる
192 |     async moveAppToSpace(appId, spaceId) {
193 |         return this.appRepo.moveAppToSpace(appId, spaceId);
194 |     }
195 | 
196 |     // アプリをスペースに所属させないようにする
197 |     async moveAppFromSpace(appId) {
198 |         return this.appRepo.moveAppFromSpace(appId);
199 |     }
200 | 
201 |     // ユーザー関連
202 |     async addGuests(guests) {
203 |         return this.userRepo.addGuests(guests);
204 |     }
205 | 
206 |     async getUsers(codes = []) {
207 |         return this.userRepo.getUsers(codes);
208 |     }
209 | 
210 |     async getGroups(codes = []) {
211 |         return this.userRepo.getGroups(codes);
212 |     }
213 | 
214 |     async getGroupUsers(groupCode) {
215 |         return this.userRepo.getGroupUsers(groupCode);
216 |     }
217 |     
218 |     // アプリのアクション設定を取得
219 |     async getAppActions(appId, lang) {
220 |         return this.appRepo.getAppActions(appId, lang);
221 |     }
222 |     
223 |     // アプリのプラグイン一覧を取得
224 |     async getAppPlugins(appId) {
225 |         return this.appRepo.getAppPlugins(appId);
226 |     }
227 | 
228 |     // アプリのプロセス管理設定を取得
229 |     async getProcessManagement(appId, preview = false) {
230 |         if (preview) {
231 |             return this.appRepo.previewRepository.getPreviewProcessManagement(appId);
232 |         } else {
233 |             return this.appRepo.getProcessManagement(appId);
234 |         }
235 |     }
236 | 
237 |     // アプリのプロセス管理設定を更新
238 |     async updateProcessManagement(appId, enable, states, actions, revision = -1) {
239 |         return this.appRepo.updateProcessManagement(appId, enable, states, actions, revision);
240 |     }
241 | 
242 |     // アプリのビュー設定を取得
243 |     async getViews(appId, preview = false) {
244 |         return this.appRepo.getViews(appId, preview);
245 |     }
246 | 
247 |     // アプリのビュー設定を更新
248 |     async updateViews(appId, views, revision = -1) {
249 |         return this.appRepo.updateViews(appId, views, revision);
250 |     }
251 | 
252 |     // アプリのアクセス権限を取得
253 |     async getAppAcl(appId, preview = false) {
254 |         return this.appRepo.getAppAcl(appId, preview);
255 |     }
256 | 
257 |     // フィールドのアクセス権限を取得
258 |     async getFieldAcl(appId, preview = false) {
259 |         return this.appRepo.getFieldAcl(appId, preview);
260 |     }
261 | 
262 |     // フィールドのアクセス権限を更新
263 |     async updateFieldAcl(appId, rights, revision = -1) {
264 |         return this.appRepo.updateFieldAcl(appId, rights, revision);
265 |     }
266 | 
267 |     // グラフ設定を取得
268 |     async getReports(appId, preview = false) {
269 |         return this.appRepo.getReports(appId, preview);
270 |     }
271 | 
272 |     // グラフ設定を更新
273 |     async updateReports(appId, reports, revision = -1) {
274 |         return this.appRepo.updateReports(appId, reports, revision);
275 |     }
276 | 
277 |     // 通知条件設定を取得
278 |     async getNotifications(appId, preview = false) {
279 |         return this.appRepo.getNotifications(appId, preview);
280 |     }
281 | 
282 |     // 通知条件設定を更新
283 |     async updateNotifications(appId, notifications, revision = -1) {
284 |         return this.appRepo.updateNotifications(appId, notifications, revision);
285 |     }
286 | 
287 |     // レコード単位の通知設定を取得
288 |     async getPerRecordNotifications(appId, preview = false) {
289 |         return this.appRepo.getPerRecordNotifications(appId, preview);
290 |     }
291 | 
292 |     // レコード単位の通知設定を更新
293 |     async updatePerRecordNotifications(appId, notifications, revision = -1) {
294 |         return this.appRepo.updatePerRecordNotifications(appId, notifications, revision);
295 |     }
296 | 
297 |     // リマインダー通知設定を取得
298 |     async getReminderNotifications(appId, preview = false) {
299 |         return this.appRepo.getReminderNotifications(appId, preview);
300 |     }
301 | 
302 |     // リマインダー通知設定を更新
303 |     async updateReminderNotifications(appId, notifications, revision = -1) {
304 |         return this.appRepo.updateReminderNotifications(appId, notifications, revision);
305 |     }
306 | 
307 |     // アプリアクション設定を更新
308 |     async updateAppActions(appId, actions, revision = -1) {
309 |         return this.appRepo.updateAppActions(appId, actions, revision);
310 |     }
311 | 
312 |     // プラグイン設定を更新
313 |     async updatePlugins(appId, plugins, revision = -1) {
314 |         return this.appRepo.updatePlugins(appId, plugins, revision);
315 |     }
316 | 
317 |     // JavaScript/CSSカスタマイズ設定を取得
318 |     async getAppCustomize(appId, preview = false) {
319 |         return this.appRepo.getAppCustomize(appId, preview);
320 |     }
321 | 
322 |     // JavaScript/CSSカスタマイズ設定を更新
323 |     async updateAppCustomize(appId, scope, desktop, mobile, revision = -1) {
324 |         return this.appRepo.updateAppCustomize(appId, scope, desktop, mobile, revision);
325 |     }
326 | 
327 |     // アプリのアクセス権限を更新
328 |     async updateAppAcl(appId, rights, revision = -1) {
329 |         return this.appRepo.updateAppAcl(appId, rights, revision);
330 |     }
331 | }
332 | 
```

--------------------------------------------------------------------------------
/src/repositories/base/http/createKintoneClient.js:
--------------------------------------------------------------------------------

```javascript
  1 | // src/repositories/base/http/createKintoneClient.js
  2 | import { KintoneHttpClient } from './KintoneHttpClient.js';
  3 | 
  4 | function removePreview(params = {}) {
  5 |     const { preview, ...rest } = params;
  6 |     return { preview: preview === true, params: rest };
  7 | }
  8 | 
  9 | export function createKintoneClient(credentials) {
 10 |     const http = new KintoneHttpClient(credentials);
 11 | 
 12 |     const space = {
 13 |         getSpace: (params) => http.get('space', params),
 14 |         updateSpace: (params) => http.put('space', params),
 15 |         updateSpaceBody: (params) => http.put('space/body', params),
 16 |         getSpaceMembers: (params) => http.get('space/members', params),
 17 |         updateSpaceMembers: (params) => http.put('space/members', params),
 18 |         addThread: (params) => http.post('space/thread', params),
 19 |         updateThread: (params) => http.put('space/thread', params),
 20 |         addThreadComment: (params) => http.post('space/thread/comment', params),
 21 |         updateSpaceGuests: (params) => http.put('space/guests', params),
 22 |         addGuests: (params) => http.post('guests', params)
 23 |     };
 24 | 
 25 |     const app = {
 26 |         getApps: (params) => http.get('apps', params),
 27 |         addApp: async (params) => {
 28 |             const payload = { name: params.name };
 29 |             if (params.space) {
 30 |                 payload.space = params.space;
 31 |                 if (params.thread) {
 32 |                     payload.thread = params.thread;
 33 |                 } else {
 34 |                     const spaceInfo = await space.getSpace({ id: params.space });
 35 |                     if (spaceInfo && spaceInfo.defaultThread) {
 36 |                         payload.thread = spaceInfo.defaultThread;
 37 |                     }
 38 |                 }
 39 |             } else if (params.thread) {
 40 |                 payload.thread = params.thread;
 41 |             }
 42 |             return http.post('app', payload, { preview: true });
 43 |         },
 44 |         deployApp: (params) => http.post('app/deploy', params, { preview: true }),
 45 |         getDeployStatus: (params) => http.get('app/deploy', params, { preview: true }),
 46 |         getAppSettings: (params = {}) => {
 47 |             const { preview, params: rest } = removePreview(params);
 48 |             return http.get('app/settings', rest, { preview });
 49 |         },
 50 |         updateAppSettings: (params) => http.put('app/settings', params, { preview: true }),
 51 |         getProcessManagement: (params = {}) => {
 52 |             const { preview, params: rest } = removePreview(params);
 53 |             return http.get('app/status', rest, { preview });
 54 |         },
 55 |         updateProcessManagement: (params) => http.put('app/status', params, { preview: true }),
 56 |         getFormFields: (params = {}) => {
 57 |             const { preview, params: rest } = removePreview(params);
 58 |             return http.get('app/form/fields', rest, { preview });
 59 |         },
 60 |         addFormFields: (params) => http.post('app/form/fields', params, { preview: true }),
 61 |         updateFormFields: (params) => http.put('app/form/fields', params, { preview: true }),
 62 |         deleteFormFields: (params) => http.delete('app/form/fields', params, { preview: true }),
 63 |         getFormLayout: (params = {}) => {
 64 |             const { preview, params: rest } = removePreview(params);
 65 |             return http.get('app/form/layout', rest, { preview });
 66 |         },
 67 |         updateFormLayout: (params) => http.put('app/form/layout', params, { preview: true }),
 68 |         getViews: (params = {}) => {
 69 |             const { preview, params: rest } = removePreview(params);
 70 |             return http.get('app/views', rest, { preview });
 71 |         },
 72 |         updateViews: (params) => http.put('app/views', params, { preview: true }),
 73 |         getAppAcl: (params = {}) => {
 74 |             const { preview, params: rest } = removePreview(params);
 75 |             return http.get('app/acl', rest, { preview });
 76 |         },
 77 |         updateAppAcl: (params) => http.put('app/acl', params, { preview: true }),
 78 |         getFieldAcl: (params = {}) => {
 79 |             const { preview, params: rest } = removePreview(params);
 80 |             return http.get('field/acl', rest, { preview });
 81 |         },
 82 |         updateFieldAcl: (params) => http.put('field/acl', params, { preview: true }),
 83 |         getAppActions: (params = {}) => {
 84 |             const { preview, params: rest } = removePreview(params);
 85 |             return http.get('app/actions', rest, { preview });
 86 |         },
 87 |         updateAppActions: (params) => http.put('app/actions', params, { preview: true }),
 88 |         getPlugins: (params = {}) => {
 89 |             const { preview, params: rest } = removePreview(params);
 90 |             return http.get('app/plugins', rest, { preview });
 91 |         },
 92 |         updatePlugins: (params) => http.put('app/plugins', params, { preview: true }),
 93 |         getReports: (params = {}) => {
 94 |             const { preview, params: rest } = removePreview(params);
 95 |             return http.get('app/reports', rest, { preview });
 96 |         },
 97 |         updateReports: (params) => http.put('app/reports', params, { preview: true }),
 98 |         getGeneralNotifications: (params = {}) => {
 99 |             const { preview, params: rest } = removePreview(params);
100 |             return http.get('app/notifications/general', rest, { preview });
101 |         },
102 |         updateGeneralNotifications: (params) => http.put('app/notifications/general', params, { preview: true }),
103 |         getPerRecordNotifications: (params = {}) => {
104 |             const { preview, params: rest } = removePreview(params);
105 |             return http.get('app/notifications/perRecord', rest, { preview });
106 |         },
107 |         updatePerRecordNotifications: (params) => http.put('app/notifications/perRecord', params, { preview: true }),
108 |         getReminderNotifications: (params = {}) => {
109 |             const { preview, params: rest } = removePreview(params);
110 |             return http.get('app/notifications/reminder', rest, { preview });
111 |         },
112 |         updateReminderNotifications: (params) => http.put('app/notifications/reminder', params, { preview: true }),
113 |         getAppCustomize: (params = {}) => {
114 |             const { preview, params: rest } = removePreview(params);
115 |             return http.get('app/customize', rest, { preview });
116 |         },
117 |         updateAppCustomize: (params) => http.put('app/customize', params, { preview: true }),
118 |         getRecordAcl: (params = {}) => {
119 |             const { preview, params: rest } = removePreview(params);
120 |             return http.get('record/acl', rest, { preview });
121 |         },
122 |         evaluateRecordsAcl: (params) => http.get('records/acl/evaluate', params),
123 |         move: (params) => http.post('app/move', params)
124 |     };
125 | 
126 |     const record = {
127 |         getRecord: (params) => http.get('record', params),
128 |         addRecord: (params) => http.post('record', params),
129 |         updateRecord: (params) => http.put('record', params),
130 |         updateRecordByUpdateKey: (params) => http.put('record', params),
131 |         getRecords: (params) => http.get('records', params),
132 |         addRecords: (params) => http.post('records', params),
133 |         updateRecords: (params) => http.put('records', params),
134 |         upsertRecord: async (params) => {
135 |             const { app: appId, updateKey, record: recordData } = params;
136 |             const response = await http.put('records', {
137 |                 app: appId,
138 |                 upsert: true,
139 |                 records: [
140 |                     {
141 |                         updateKey,
142 |                         record: recordData
143 |                     }
144 |                 ]
145 |             });
146 | 
147 |             if (!response || !Array.isArray(response.records) || response.records.length === 0) {
148 |                 throw new Error('Unexpected response format from records upsert operation.');
149 |             }
150 | 
151 |             const result = response.records[0];
152 |             return {
153 |                 id: result.id,
154 |                 revision: result.revision,
155 |                 operation: result.operation
156 |             };
157 |         },
158 |         upsertRecords: async (params) => {
159 |             const { app: appId, records } = params;
160 |             if (!Array.isArray(records) || records.length === 0) {
161 |                 throw new Error('records must be a non-empty array when calling upsertRecords.');
162 |             }
163 | 
164 |             const formattedRecords = records.map((entry) => ({
165 |                 updateKey: entry.updateKey,
166 |                 record: entry.record
167 |             }));
168 | 
169 |             const response = await http.put('records', {
170 |                 app: appId,
171 |                 upsert: true,
172 |                 records: formattedRecords
173 |             });
174 | 
175 |             if (!response || !Array.isArray(response.records) || response.records.length === 0) {
176 |                 throw new Error('Unexpected response format from records upsert operation.');
177 |             }
178 | 
179 |             return response.records.map((recordResult) => ({
180 |                 id: recordResult.id,
181 |                 revision: recordResult.revision,
182 |                 operation: recordResult.operation
183 |             }));
184 |         },
185 |         addRecordComment: (params) => http.post('record/comment', params),
186 |         getRecordComments: (params) => http.get('record/comments', params),
187 |         updateRecordComment: (params) => http.put('record/comment', params),
188 |         updateRecordStatus: (params) => http.put('record/status', params),
189 |         updateRecordAssignees: (params) => http.put('record/assignees', params)
190 |     };
191 | 
192 |     const file = {
193 |         uploadFile: async (params) => {
194 |             const form = new FormData();
195 |             const fileData = params.file?.data;
196 |             const fileName = params.file?.name;
197 | 
198 |             if (fileData === undefined || fileData === null) {
199 |                 throw new Error('Missing file data for upload.');
200 |             }
201 | 
202 |             const blob = fileData instanceof Blob ? fileData : new Blob([fileData]);
203 |             form.append('file', blob, fileName ?? 'upload.bin');
204 |             return http.post('file', form);
205 |         },
206 |         downloadFile: async (params) => {
207 |             const response = await http.get('file', params, {
208 |                 responseType: 'arraybuffer',
209 |                 rawResponse: true
210 |             });
211 |             const contentType = response.headers['content-type'] || response.headers['Content-Type'];
212 |             return {
213 |                 data: response.data,
214 |                 contentType
215 |             };
216 |         }
217 |     };
218 | 
219 |     return { app, record, space, file };
220 | }
221 | 
```
Page 1/3FirstPrevNextLast