backlog-readonly-mcp-blog 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +57 -0
- package/.github/settings.yml +49 -0
- package/.husky/pre-commit +1 -0
- package/.kiro/hooks/agent-completion-sound.kiro.hook +15 -0
- package/.kiro/hooks/markdown-lint-save.kiro.hook +19 -0
- package/.kiro/settings/locale.json +3 -0
- package/.kiro/settings/mcp.json +32 -0
- package/.kiro/specs/backlog-readonly-mcp/design.md +285 -0
- package/.kiro/specs/backlog-readonly-mcp/requirements.md +158 -0
- package/.kiro/specs/backlog-readonly-mcp/tasks.md +224 -0
- package/.kiro/steering/blog-evaluation.md +103 -0
- package/.kiro/steering/pr-review-response.md +29 -0
- package/.markdownlint.json +5 -0
- package/.textlintrc.json +32 -0
- package/.vscode/settings.json +47 -0
- package/README.md +170 -0
- package/biome.json +36 -0
- package/blog_content/blog.md +57 -0
- package/package.json +49 -0
- package/packages/backlog-readonly-mcp/.backlog-mcp.env.example +49 -0
- package/packages/backlog-readonly-mcp/MCP_CLIENT_SETUP.md +303 -0
- package/packages/backlog-readonly-mcp/README.md +259 -0
- package/packages/backlog-readonly-mcp/package.json +58 -0
- package/packages/backlog-readonly-mcp/src/client/backlog-api-client.ts +348 -0
- package/packages/backlog-readonly-mcp/src/config/config-manager.ts +280 -0
- package/packages/backlog-readonly-mcp/src/index.ts +247 -0
- package/packages/backlog-readonly-mcp/src/tools/issue-tools.ts +449 -0
- package/packages/backlog-readonly-mcp/src/tools/master-data-tools.ts +209 -0
- package/packages/backlog-readonly-mcp/src/tools/project-tools.ts +219 -0
- package/packages/backlog-readonly-mcp/src/tools/tool-registry.ts +223 -0
- package/packages/backlog-readonly-mcp/src/tools/user-tools.ts +111 -0
- package/packages/backlog-readonly-mcp/src/tools/wiki-tools.ts +149 -0
- package/packages/backlog-readonly-mcp/src/types/index.ts +297 -0
- package/packages/backlog-readonly-mcp/src/utils/logger.ts +123 -0
- package/packages/backlog-readonly-mcp/test/backlog-api-client.test.ts +307 -0
- package/packages/backlog-readonly-mcp/test/config-manager.test.ts +303 -0
- package/packages/backlog-readonly-mcp/test/issue-tools.test.ts +345 -0
- package/packages/backlog-readonly-mcp/test/mcp-server-integration.test.ts +254 -0
- package/packages/backlog-readonly-mcp/test/mcp-server.test.ts +91 -0
- package/packages/backlog-readonly-mcp/test/project-tools.test.ts +194 -0
- package/packages/backlog-readonly-mcp/test/workspace-config.test.ts +320 -0
- package/packages/backlog-readonly-mcp/tsconfig.json +21 -0
- package/packages/backlog-readonly-mcp/tsconfig.prod.json +11 -0
- package/packages/backlog-readonly-mcp/vitest.config.ts +12 -0
- package/packages/cdk/README.md +14 -0
- package/packages/cdk/cdk.json +98 -0
- package/packages/cdk/jest.config.js +9 -0
- package/packages/cdk/node_modules/.bin/browserslist +21 -0
- package/packages/cdk/node_modules/.bin/cdk +21 -0
- package/packages/cdk/node_modules/.bin/jest +21 -0
- package/packages/cdk/node_modules/.bin/ts-jest +21 -0
- package/packages/cdk/node_modules/.bin/ts-node +21 -0
- package/packages/cdk/node_modules/.bin/ts-node-cwd +21 -0
- package/packages/cdk/node_modules/.bin/ts-node-esm +21 -0
- package/packages/cdk/node_modules/.bin/ts-node-script +21 -0
- package/packages/cdk/node_modules/.bin/ts-node-transpile-only +21 -0
- package/packages/cdk/node_modules/.bin/ts-script +21 -0
- package/packages/cdk/node_modules/.bin/tsc +21 -0
- package/packages/cdk/node_modules/.bin/tsserver +21 -0
- package/packages/cdk/package.json +31 -0
- package/packages/cdk/test/__snapshots__/cdk.test.ts.snap +40 -0
- package/packages/cdk/tsconfig.json +25 -0
- package/pnpm-workspace.yaml +2 -0
- package/scripts/setup-blog.js +148 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ユーザー関連ツール
|
|
3
|
+
*
|
|
4
|
+
* Backlogのユーザー情報を取得するためのツールを提供します。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BacklogApiClient } from '../client/backlog-api-client.js';
|
|
8
|
+
import type { BacklogUser } from '../types/index.js';
|
|
9
|
+
import type { ToolRegistry } from './tool-registry.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* ユーザー関連ツールを登録します
|
|
13
|
+
*/
|
|
14
|
+
export function registerUserTools(
|
|
15
|
+
toolRegistry: ToolRegistry,
|
|
16
|
+
apiClient: BacklogApiClient,
|
|
17
|
+
): void {
|
|
18
|
+
// ユーザー一覧取得ツール
|
|
19
|
+
toolRegistry.registerTool(
|
|
20
|
+
{
|
|
21
|
+
name: 'get_users',
|
|
22
|
+
description: 'ユーザー一覧を取得します(読み取り専用)',
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {},
|
|
26
|
+
required: [],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
async () => {
|
|
30
|
+
try {
|
|
31
|
+
const users = await apiClient.get<BacklogUser[]>('/users');
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
success: true,
|
|
35
|
+
data: users,
|
|
36
|
+
count: users.length,
|
|
37
|
+
message: `${users.length}名のユーザーを取得しました`,
|
|
38
|
+
};
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`ユーザー一覧の取得に失敗しました: ${error instanceof Error ? error.message : '不明なエラー'}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// ユーザー詳細取得ツール
|
|
48
|
+
toolRegistry.registerTool(
|
|
49
|
+
{
|
|
50
|
+
name: 'get_user',
|
|
51
|
+
description: 'ユーザーの詳細情報を取得します(読み取り専用)',
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
userId: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
description: 'ユーザーID(数値IDまたはユーザーキー)',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
required: ['userId'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
async (args) => {
|
|
64
|
+
const { userId } = args as { userId: string };
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const user = await apiClient.get<BacklogUser>(
|
|
68
|
+
`/users/${encodeURIComponent(userId)}`,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
data: user,
|
|
74
|
+
message: `ユーザー "${user.name}" の詳細情報を取得しました`,
|
|
75
|
+
};
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`ユーザー詳細の取得に失敗しました: ${error instanceof Error ? error.message : '不明なエラー'}`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// 自分のユーザー情報取得ツール
|
|
85
|
+
toolRegistry.registerTool(
|
|
86
|
+
{
|
|
87
|
+
name: 'get_myself',
|
|
88
|
+
description: '自分のユーザー情報を取得します(読み取り専用)',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {},
|
|
92
|
+
required: [],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
async () => {
|
|
96
|
+
try {
|
|
97
|
+
const user = await apiClient.get<BacklogUser>('/users/myself');
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
data: user,
|
|
102
|
+
message: `自分のユーザー情報を取得しました: ${user.name}`,
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`自分のユーザー情報の取得に失敗しました: ${error instanceof Error ? error.message : '不明なエラー'}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wiki関連ツール
|
|
3
|
+
*
|
|
4
|
+
* BacklogのWiki情報を取得するためのツールを提供します。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { BacklogApiClient } from '../client/backlog-api-client.js';
|
|
8
|
+
import { ConfigManager } from '../config/config-manager.js';
|
|
9
|
+
import type { BacklogWiki } from '../types/index.js';
|
|
10
|
+
import type { ToolRegistry } from './tool-registry.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Wiki関連ツールを登録します
|
|
14
|
+
*/
|
|
15
|
+
export function registerWikiTools(
|
|
16
|
+
toolRegistry: ToolRegistry,
|
|
17
|
+
apiClient: BacklogApiClient,
|
|
18
|
+
): void {
|
|
19
|
+
// Wiki一覧取得ツール
|
|
20
|
+
toolRegistry.registerTool(
|
|
21
|
+
{
|
|
22
|
+
name: 'get_wikis',
|
|
23
|
+
description:
|
|
24
|
+
'Wiki一覧を取得します(読み取り専用)。プロジェクトIDまたはキーを省略した場合、デフォルトプロジェクトを使用します。大量のWikiがあるプロジェクトではキーワード検索の使用を推奨します。',
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: 'object',
|
|
27
|
+
properties: {
|
|
28
|
+
projectIdOrKey: {
|
|
29
|
+
type: 'string',
|
|
30
|
+
description:
|
|
31
|
+
'プロジェクトIDまたはプロジェクトキー(例: "MYPROJ" または "123")。省略時はデフォルトプロジェクトを使用。',
|
|
32
|
+
},
|
|
33
|
+
keyword: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description:
|
|
36
|
+
'キーワード検索(Wiki名と内容を対象)。大量のWikiがあるプロジェクトでは指定を推奨。',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
required: [],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
async (args) => {
|
|
43
|
+
const { projectIdOrKey, keyword } = args as {
|
|
44
|
+
projectIdOrKey?: string;
|
|
45
|
+
keyword?: string;
|
|
46
|
+
};
|
|
47
|
+
const configManager = ConfigManager.getInstance();
|
|
48
|
+
|
|
49
|
+
const params: Record<string, unknown> = {};
|
|
50
|
+
if (keyword) {
|
|
51
|
+
params.keyword = keyword;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const resolvedProjectIdOrKey =
|
|
56
|
+
configManager.resolveProjectIdOrKey(projectIdOrKey);
|
|
57
|
+
|
|
58
|
+
const wikis = await apiClient.get<BacklogWiki[]>(
|
|
59
|
+
`/projects/${encodeURIComponent(resolvedProjectIdOrKey)}/wikis`,
|
|
60
|
+
params,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const isDefaultProject =
|
|
64
|
+
!projectIdOrKey && configManager.hasDefaultProject();
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
success: true,
|
|
68
|
+
data: wikis,
|
|
69
|
+
count: wikis.length,
|
|
70
|
+
message: `${wikis.length}件のWikiページを取得しました${isDefaultProject ? '(デフォルトプロジェクト)' : ''}`,
|
|
71
|
+
isDefaultProject,
|
|
72
|
+
searchParams: params,
|
|
73
|
+
};
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (
|
|
76
|
+
error instanceof Error &&
|
|
77
|
+
error.message.includes('デフォルトプロジェクト')
|
|
78
|
+
) {
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 504エラー(Gateway Timeout)の場合は、データが多すぎることを示すメッセージを返す
|
|
83
|
+
// AxiosErrorの場合はHTTPステータスコードを直接チェック
|
|
84
|
+
const isAxiosError =
|
|
85
|
+
error && typeof error === 'object' && 'response' in error;
|
|
86
|
+
const is504Error =
|
|
87
|
+
isAxiosError &&
|
|
88
|
+
(error as { response?: { status?: number } }).response?.status ===
|
|
89
|
+
504;
|
|
90
|
+
|
|
91
|
+
if (is504Error) {
|
|
92
|
+
const isDefaultProject =
|
|
93
|
+
!projectIdOrKey && configManager.hasDefaultProject();
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
success: false,
|
|
97
|
+
data: [],
|
|
98
|
+
count: 0,
|
|
99
|
+
message: `プロジェクトのWikiページが多すぎて取得に時間がかかりすぎました。キーワード検索を使用して絞り込んでください${isDefaultProject ? '(デフォルトプロジェクト)' : ''}`,
|
|
100
|
+
isDefaultProject,
|
|
101
|
+
searchParams: params,
|
|
102
|
+
error: 'TIMEOUT_TOO_MANY_WIKIS',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Wiki一覧の取得に失敗しました: ${error instanceof Error ? error.message : '不明なエラー'}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// 特定Wiki取得ツール
|
|
114
|
+
toolRegistry.registerTool(
|
|
115
|
+
{
|
|
116
|
+
name: 'get_wiki',
|
|
117
|
+
description: '特定のWikiページを取得します(読み取り専用)',
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
wikiId: {
|
|
122
|
+
type: 'string',
|
|
123
|
+
description: 'WikiのID(数値)',
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
required: ['wikiId'],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
async (args) => {
|
|
130
|
+
const { wikiId } = args as { wikiId: string };
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const wiki = await apiClient.get<BacklogWiki>(
|
|
134
|
+
`/wikis/${encodeURIComponent(wikiId)}`,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
success: true,
|
|
139
|
+
data: wiki,
|
|
140
|
+
message: `Wikiページ "${wiki.name}" を取得しました`,
|
|
141
|
+
};
|
|
142
|
+
} catch (error) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Wikiページの取得に失敗しました: ${error instanceof Error ? error.message : '不明なエラー'}`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backlog読み取り専用MCPサーバーの型定義
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// 設定関連の型
|
|
6
|
+
export interface BacklogConfig {
|
|
7
|
+
/** Backlogドメイン (例: your-company.backlog.com) */
|
|
8
|
+
domain: string;
|
|
9
|
+
/** APIキー */
|
|
10
|
+
apiKey: string;
|
|
11
|
+
/** デフォルトプロジェクトキー (オプション) */
|
|
12
|
+
defaultProject?: string;
|
|
13
|
+
/** リトライ回数 */
|
|
14
|
+
maxRetries: number;
|
|
15
|
+
/** タイムアウト(ミリ秒) */
|
|
16
|
+
timeout: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Backlog API レスポンス型
|
|
20
|
+
export interface BacklogProject {
|
|
21
|
+
id: number;
|
|
22
|
+
projectKey: string;
|
|
23
|
+
name: string;
|
|
24
|
+
chartEnabled: boolean;
|
|
25
|
+
subtaskingEnabled: boolean;
|
|
26
|
+
projectLeaderCanEditProjectLeader: boolean;
|
|
27
|
+
useWikiTreeView: boolean;
|
|
28
|
+
textFormattingRule: string;
|
|
29
|
+
archived: boolean;
|
|
30
|
+
displayOrder: number;
|
|
31
|
+
useDevAttributes: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface BacklogUser {
|
|
35
|
+
id: number;
|
|
36
|
+
userId: string;
|
|
37
|
+
name: string;
|
|
38
|
+
roleType: number;
|
|
39
|
+
lang: string;
|
|
40
|
+
mailAddress: string;
|
|
41
|
+
nulabAccount?: {
|
|
42
|
+
nulabId: string;
|
|
43
|
+
name: string;
|
|
44
|
+
uniqueId: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface BacklogIssue {
|
|
49
|
+
id: number;
|
|
50
|
+
projectId: number;
|
|
51
|
+
issueKey: string;
|
|
52
|
+
keyId: number;
|
|
53
|
+
issueType: IssueType;
|
|
54
|
+
summary: string;
|
|
55
|
+
description: string;
|
|
56
|
+
resolution: Resolution | null;
|
|
57
|
+
priority: Priority;
|
|
58
|
+
status: Status;
|
|
59
|
+
assignee: BacklogUser | null;
|
|
60
|
+
category: Category[];
|
|
61
|
+
versions: Version[];
|
|
62
|
+
milestone: Milestone[];
|
|
63
|
+
startDate: string | null;
|
|
64
|
+
dueDate: string | null;
|
|
65
|
+
estimatedHours: number | null;
|
|
66
|
+
actualHours: number | null;
|
|
67
|
+
parentIssueId: number | null;
|
|
68
|
+
createdUser: BacklogUser;
|
|
69
|
+
created: string;
|
|
70
|
+
updatedUser: BacklogUser;
|
|
71
|
+
updated: string;
|
|
72
|
+
customFields: CustomField[];
|
|
73
|
+
attachments: Attachment[];
|
|
74
|
+
sharedFiles: SharedFile[];
|
|
75
|
+
stars: Star[];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface IssueType {
|
|
79
|
+
id: number;
|
|
80
|
+
projectId: number;
|
|
81
|
+
name: string;
|
|
82
|
+
color: string;
|
|
83
|
+
displayOrder: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface Resolution {
|
|
87
|
+
id: number;
|
|
88
|
+
name: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface Priority {
|
|
92
|
+
id: number;
|
|
93
|
+
name: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface Status {
|
|
97
|
+
id: number;
|
|
98
|
+
projectId: number;
|
|
99
|
+
name: string;
|
|
100
|
+
color: string;
|
|
101
|
+
displayOrder: number;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface Category {
|
|
105
|
+
id: number;
|
|
106
|
+
name: string;
|
|
107
|
+
displayOrder: number;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface Version {
|
|
111
|
+
id: number;
|
|
112
|
+
projectId: number;
|
|
113
|
+
name: string;
|
|
114
|
+
description: string;
|
|
115
|
+
startDate: string | null;
|
|
116
|
+
releaseDueDate: string | null;
|
|
117
|
+
archived: boolean;
|
|
118
|
+
displayOrder: number;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface Milestone {
|
|
122
|
+
id: number;
|
|
123
|
+
projectId: number;
|
|
124
|
+
name: string;
|
|
125
|
+
description: string;
|
|
126
|
+
startDate: string | null;
|
|
127
|
+
releaseDueDate: string | null;
|
|
128
|
+
archived: boolean;
|
|
129
|
+
displayOrder: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface CustomField {
|
|
133
|
+
id: number;
|
|
134
|
+
typeId: number;
|
|
135
|
+
name: string;
|
|
136
|
+
description: string;
|
|
137
|
+
required: boolean;
|
|
138
|
+
applicableIssueTypes: number[];
|
|
139
|
+
allowAddItem: boolean;
|
|
140
|
+
items: CustomFieldItem[];
|
|
141
|
+
value?: string | number | string[] | number[] | null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface CustomFieldItem {
|
|
145
|
+
id: number;
|
|
146
|
+
name: string;
|
|
147
|
+
displayOrder: number;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface Attachment {
|
|
151
|
+
id: number;
|
|
152
|
+
name: string;
|
|
153
|
+
size: number;
|
|
154
|
+
createdUser: BacklogUser;
|
|
155
|
+
created: string;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface SharedFile {
|
|
159
|
+
id: number;
|
|
160
|
+
type: string;
|
|
161
|
+
dir: string;
|
|
162
|
+
name: string;
|
|
163
|
+
size: number;
|
|
164
|
+
createdUser: BacklogUser;
|
|
165
|
+
created: string;
|
|
166
|
+
updatedUser: BacklogUser;
|
|
167
|
+
updated: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface Star {
|
|
171
|
+
id: number;
|
|
172
|
+
comment: string | null;
|
|
173
|
+
url: string;
|
|
174
|
+
title: string;
|
|
175
|
+
presenter: BacklogUser;
|
|
176
|
+
created: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface BacklogComment {
|
|
180
|
+
id: number;
|
|
181
|
+
content: string;
|
|
182
|
+
changeLog: ChangeLog[];
|
|
183
|
+
createdUser: BacklogUser;
|
|
184
|
+
created: string;
|
|
185
|
+
updated: string;
|
|
186
|
+
stars: Star[];
|
|
187
|
+
notifications: Notification[];
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface ChangeLog {
|
|
191
|
+
field: string;
|
|
192
|
+
newValue: string;
|
|
193
|
+
originalValue: string;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface Notification {
|
|
197
|
+
id: number;
|
|
198
|
+
alreadyRead: boolean;
|
|
199
|
+
reason: number;
|
|
200
|
+
user: BacklogUser;
|
|
201
|
+
resourceAlreadyRead: boolean;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface BacklogWiki {
|
|
205
|
+
id: number;
|
|
206
|
+
projectId: number;
|
|
207
|
+
name: string;
|
|
208
|
+
content: string;
|
|
209
|
+
tags: WikiTag[];
|
|
210
|
+
attachments: Attachment[];
|
|
211
|
+
sharedFiles: SharedFile[];
|
|
212
|
+
stars: Star[];
|
|
213
|
+
createdUser: BacklogUser;
|
|
214
|
+
created: string;
|
|
215
|
+
updatedUser: BacklogUser;
|
|
216
|
+
updated: string;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface WikiTag {
|
|
220
|
+
id: number;
|
|
221
|
+
name: string;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// エラー関連の型
|
|
225
|
+
export interface BacklogError {
|
|
226
|
+
code: string;
|
|
227
|
+
message: string;
|
|
228
|
+
details?: unknown;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface MCPError {
|
|
232
|
+
code: number;
|
|
233
|
+
message: string;
|
|
234
|
+
data?: unknown;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// カスタムエラークラス
|
|
238
|
+
export class ReadOnlyViolationError extends Error {
|
|
239
|
+
constructor(message: string) {
|
|
240
|
+
super(message);
|
|
241
|
+
this.name = 'ReadOnlyViolationError';
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export class AuthenticationError extends Error {
|
|
246
|
+
constructor(message: string) {
|
|
247
|
+
super(message);
|
|
248
|
+
this.name = 'AuthenticationError';
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export class NetworkError extends Error {
|
|
253
|
+
constructor(message: string) {
|
|
254
|
+
super(message);
|
|
255
|
+
this.name = 'NetworkError';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* シンプルな JSON Schema 型定義
|
|
261
|
+
*
|
|
262
|
+
* - 学習用・検証用のため、よく使うプロパティのみに絞っています
|
|
263
|
+
* - 必要に応じてプロパティを拡張していく想定です
|
|
264
|
+
*/
|
|
265
|
+
export interface JSONSchema {
|
|
266
|
+
type:
|
|
267
|
+
| 'string'
|
|
268
|
+
| 'number'
|
|
269
|
+
| 'integer'
|
|
270
|
+
| 'boolean'
|
|
271
|
+
| 'object'
|
|
272
|
+
| 'array'
|
|
273
|
+
| 'null';
|
|
274
|
+
description?: string;
|
|
275
|
+
properties?: Record<string, JSONSchema>;
|
|
276
|
+
items?: JSONSchema;
|
|
277
|
+
enum?: Array<string | number | boolean | null>;
|
|
278
|
+
required?: string[];
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ツール関連の型
|
|
282
|
+
export interface ToolDefinition {
|
|
283
|
+
name: string;
|
|
284
|
+
description: string;
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: 'object';
|
|
287
|
+
properties: Record<string, JSONSchema>;
|
|
288
|
+
required?: string[];
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface ToolResult {
|
|
293
|
+
content: Array<{
|
|
294
|
+
type: 'text';
|
|
295
|
+
text: string;
|
|
296
|
+
}>;
|
|
297
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ログユーティリティ
|
|
3
|
+
*
|
|
4
|
+
* MCPサーバーのログ出力を管理します。
|
|
5
|
+
* 要件7.3: 重要な操作とエラーをログに記録する
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export enum LogLevel {
|
|
9
|
+
ERROR = 0,
|
|
10
|
+
WARN = 1,
|
|
11
|
+
INFO = 2,
|
|
12
|
+
DEBUG = 3,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let logLevel: LogLevel = LogLevel.INFO;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* ログレベルを設定
|
|
19
|
+
*/
|
|
20
|
+
export function setLogLevel(level: LogLevel): void {
|
|
21
|
+
logLevel = level;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* エラーログ
|
|
26
|
+
*/
|
|
27
|
+
export function error(message: string, ...args: unknown[]): void {
|
|
28
|
+
if (logLevel >= LogLevel.ERROR) {
|
|
29
|
+
const timestamp = new Date().toISOString();
|
|
30
|
+
console.error(`[ERROR] ${timestamp} ${message}`, ...args);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 警告ログ
|
|
36
|
+
*/
|
|
37
|
+
export function warn(message: string, ...args: unknown[]): void {
|
|
38
|
+
if (logLevel >= LogLevel.WARN) {
|
|
39
|
+
const timestamp = new Date().toISOString();
|
|
40
|
+
console.warn(`[WARN] ${timestamp} ${message}`, ...args);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 情報ログ
|
|
46
|
+
*/
|
|
47
|
+
export function info(message: string, ...args: unknown[]): void {
|
|
48
|
+
if (logLevel >= LogLevel.INFO) {
|
|
49
|
+
const timestamp = new Date().toISOString();
|
|
50
|
+
console.log(`[INFO] ${timestamp} ${message}`, ...args);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* デバッグログ
|
|
56
|
+
*/
|
|
57
|
+
export function debug(message: string, ...args: unknown[]): void {
|
|
58
|
+
if (logLevel >= LogLevel.DEBUG) {
|
|
59
|
+
const timestamp = new Date().toISOString();
|
|
60
|
+
console.log(`[DEBUG] ${timestamp} ${message}`, ...args);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* API操作のログ記録
|
|
66
|
+
*/
|
|
67
|
+
export function logApiOperation(
|
|
68
|
+
operation: string,
|
|
69
|
+
endpoint: string,
|
|
70
|
+
params?: Record<string, unknown>,
|
|
71
|
+
duration?: number,
|
|
72
|
+
): void {
|
|
73
|
+
const message = `API操作: ${operation} ${endpoint}`;
|
|
74
|
+
const details = {
|
|
75
|
+
params: params ? Object.keys(params) : undefined,
|
|
76
|
+
duration: duration ? `${duration}ms` : undefined,
|
|
77
|
+
};
|
|
78
|
+
info(message, details);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* エラー詳細のログ記録
|
|
83
|
+
*/
|
|
84
|
+
export function logError(
|
|
85
|
+
context: string,
|
|
86
|
+
err: unknown,
|
|
87
|
+
additionalInfo?: Record<string, unknown>,
|
|
88
|
+
): void {
|
|
89
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
90
|
+
const errorStack = err instanceof Error ? err.stack : undefined;
|
|
91
|
+
|
|
92
|
+
error(`${context}: ${errorMessage}`, {
|
|
93
|
+
stack: errorStack,
|
|
94
|
+
...additionalInfo,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* レート制限のログ記録
|
|
100
|
+
*/
|
|
101
|
+
export function logRateLimit(retryAfter: number, attempt: number): void {
|
|
102
|
+
warn(
|
|
103
|
+
`レート制限に達しました。${retryAfter}秒後にリトライします (試行回数: ${attempt})`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 設定読み込みのログ記録
|
|
109
|
+
*/
|
|
110
|
+
export function logConfigLoad(
|
|
111
|
+
source: string,
|
|
112
|
+
success: boolean,
|
|
113
|
+
details?: string,
|
|
114
|
+
): void {
|
|
115
|
+
if (success) {
|
|
116
|
+
info(`設定を読み込みました: ${source}`, details ? { details } : undefined);
|
|
117
|
+
} else {
|
|
118
|
+
warn(
|
|
119
|
+
`設定の読み込みに失敗しました: ${source}`,
|
|
120
|
+
details ? { details } : undefined,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|