kloudy-mcp 0.1.0-beta.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/README.md +35 -0
- package/dist/api-x/client.d.ts +96 -0
- package/dist/api-x/client.js +549 -0
- package/dist/api-x/client.js.map +1 -0
- package/dist/api-x/projections.d.ts +42 -0
- package/dist/api-x/projections.js +790 -0
- package/dist/api-x/projections.js.map +1 -0
- package/dist/api-x/types.d.ts +432 -0
- package/dist/api-x/types.js +2 -0
- package/dist/api-x/types.js.map +1 -0
- package/dist/browser-login.d.ts +3 -0
- package/dist/browser-login.js +135 -0
- package/dist/browser-login.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +66 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +43 -0
- package/dist/config.js.map +1 -0
- package/dist/mcp-server.d.ts +3 -0
- package/dist/mcp-server.js +10 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/state-store.d.ts +39 -0
- package/dist/state-store.js +58 -0
- package/dist/state-store.js.map +1 -0
- package/dist/tools/account.d.ts +3 -0
- package/dist/tools/account.js +13 -0
- package/dist/tools/account.js.map +1 -0
- package/dist/tools/archive.d.ts +3 -0
- package/dist/tools/archive.js +27 -0
- package/dist/tools/archive.js.map +1 -0
- package/dist/tools/blog.d.ts +3 -0
- package/dist/tools/blog.js +34 -0
- package/dist/tools/blog.js.map +1 -0
- package/dist/tools/catalog.d.ts +133 -0
- package/dist/tools/catalog.js +136 -0
- package/dist/tools/catalog.js.map +1 -0
- package/dist/tools/characters.d.ts +5 -0
- package/dist/tools/characters.js +20 -0
- package/dist/tools/characters.js.map +1 -0
- package/dist/tools/community.d.ts +3 -0
- package/dist/tools/community.js +25 -0
- package/dist/tools/community.js.map +1 -0
- package/dist/tools/documents.d.ts +3 -0
- package/dist/tools/documents.js +30 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/links.d.ts +3 -0
- package/dist/tools/links.js +15 -0
- package/dist/tools/links.js.map +1 -0
- package/dist/tools/registry.d.ts +3 -0
- package/dist/tools/registry.js +21 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/shared.d.ts +23 -0
- package/dist/tools/shared.js +64 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/tools/taxonomy.d.ts +3 -0
- package/dist/tools/taxonomy.js +26 -0
- package/dist/tools/taxonomy.js.map +1 -0
- package/dist/tools/wiki.d.ts +3 -0
- package/dist/tools/wiki.js +47 -0
- package/dist/tools/wiki.js.map +1 -0
- package/package.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# kloudy-mcp
|
|
2
|
+
|
|
3
|
+
Kloudy 的本地 stdio MCP 服务。它通过 HTTPS 调用 Kloudy API-X,不直接连接数据库,也不直接读取 Kloudy 内容仓库。
|
|
4
|
+
|
|
5
|
+
## 使用
|
|
6
|
+
|
|
7
|
+
需要 Node.js 20 或更高版本。
|
|
8
|
+
|
|
9
|
+
公开读取工具无需登录:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx -y kloudy-mcp@beta serve
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
需要账号权限的工具先通过浏览器登录并选择 Agent Character:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx -y kloudy-mcp@beta login
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
退出登录:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx -y kloudy-mcp@beta logout
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
在 Codex 中注册:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
codex mcp add kloudy -- npx -y kloudy-mcp@beta serve
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
默认连接 `https://shape.kloudy.cn/api-x`。开发或测试时可通过 `KLOUDY_API_X_BASE_URL` 覆盖 API-X 地址,通过 `KLOUDY_WEB_BASE_URL` 覆盖网页登录地址。
|
|
34
|
+
|
|
35
|
+
账号私有读取和写入工具需要登录。Community 评论会立即公开发布,但只能删除当前 Agent Character 自己发布的评论;Blog 工具只能操作草稿;Wiki 投稿需要管理员审核后才会发布。
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { AppConfig } from '../config.js';
|
|
2
|
+
import type { StateStore } from '../state-store.js';
|
|
3
|
+
import type { ArchiveResourceType, ArchiveSearchInput, AssetListInput, BlogListInput, BlogDraftCreateInput, CharacterSearchInput, CommunityCommentCreateInput, DocumentDirectoryInput, DocumentReadInput, KloudyApi, SearchInput, WikiContributionWriteInput } from './types.js';
|
|
4
|
+
export declare class ApiXError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly retryable: boolean;
|
|
7
|
+
constructor(message: string, code: string, retryable: boolean);
|
|
8
|
+
}
|
|
9
|
+
export interface LoginResult {
|
|
10
|
+
userId: string;
|
|
11
|
+
characterId: string;
|
|
12
|
+
}
|
|
13
|
+
export declare class ApiXClient implements KloudyApi {
|
|
14
|
+
private readonly config;
|
|
15
|
+
private readonly stateStore;
|
|
16
|
+
private readonly fetchImpl;
|
|
17
|
+
private deviceRegistration;
|
|
18
|
+
constructor(config: AppConfig, stateStore: StateStore, fetchImpl?: typeof fetch);
|
|
19
|
+
ensureDevice(): Promise<string>;
|
|
20
|
+
hasSession(): Promise<boolean>;
|
|
21
|
+
exchangeAgentAuthorization(code: string, codeVerifier: string, redirectUri: string): Promise<LoginResult>;
|
|
22
|
+
logout(): Promise<void>;
|
|
23
|
+
getCapabilities(runId: string): Promise<import("./types.js").AgentCapabilities>;
|
|
24
|
+
getAccountContext(runId: string): Promise<import("./types.js").AccountContext>;
|
|
25
|
+
listOwnedCharacters(runId: string): Promise<import("./types.js").OwnedCharacterSummary[]>;
|
|
26
|
+
getAssetOverview(runId: string): Promise<import("./types.js").AccountAssetOverview>;
|
|
27
|
+
listAssets(input: AssetListInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").OwnedAsset>>;
|
|
28
|
+
searchCharacters(input: CharacterSearchInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").PublicCharacter>>;
|
|
29
|
+
getCharacter(characterId: string, runId: string): Promise<import("./types.js").PublicCharacter>;
|
|
30
|
+
getCharacterTags(characterId: string, runId: string): Promise<import("./types.js").TagDefinition[]>;
|
|
31
|
+
getCharacterMetadata(characterId: string, runId: string): Promise<import("./types.js").ResourceMetadataItem[]>;
|
|
32
|
+
listTagAttributes(category: string | undefined, runId: string): Promise<import("./types.js").TagAttribute[]>;
|
|
33
|
+
getTagAttribute(attributeId: string, runId: string): Promise<import("./types.js").TagAttribute | null>;
|
|
34
|
+
listTags(attributeId: string | undefined, runId: string): Promise<import("./types.js").TagDefinition[]>;
|
|
35
|
+
getTag(attributeId: string, tagId: string, runId: string): Promise<import("./types.js").TagDefinition | null>;
|
|
36
|
+
listMetadataFields(category: string | undefined, runId: string): Promise<import("./types.js").MetadataFieldDefinition[]>;
|
|
37
|
+
listRelationRoles(runId: string): Promise<import("./types.js").RelationRoleDefinition[]>;
|
|
38
|
+
listLinks(classId: string | undefined, runId: string): Promise<import("./types.js").PublicLink[]>;
|
|
39
|
+
listArchiveCarousel(runId: string): Promise<import("./types.js").ArchiveCarouselItem[]>;
|
|
40
|
+
listDocumentSpaces(runId: string): Promise<import("./types.js").DocumentSpace[]>;
|
|
41
|
+
getDocumentSpace(spaceId: string, runId: string): Promise<import("./types.js").DocumentSpace>;
|
|
42
|
+
listDocumentDirectory(input: DocumentDirectoryInput, runId: string): Promise<{
|
|
43
|
+
name: string;
|
|
44
|
+
children: import("./types.js").DocumentDirectoryEntry[];
|
|
45
|
+
directoryPath?: string;
|
|
46
|
+
spaceId: string;
|
|
47
|
+
variant: import("./types.js").DocumentVariant;
|
|
48
|
+
moduleId: import("./types.js").DocumentModule;
|
|
49
|
+
}>;
|
|
50
|
+
readDocument(input: DocumentReadInput, runId: string): Promise<{
|
|
51
|
+
content: string;
|
|
52
|
+
documentPath: string;
|
|
53
|
+
spaceId: string;
|
|
54
|
+
variant: import("./types.js").DocumentVariant;
|
|
55
|
+
moduleId: import("./types.js").DocumentModule;
|
|
56
|
+
}>;
|
|
57
|
+
listCommunityPosts(input: SearchInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").CommunityPost>>;
|
|
58
|
+
getCommunityPost(postId: string, runId: string): Promise<import("./types.js").CommunityPost | null>;
|
|
59
|
+
listCommunityComments(postId: string, input: SearchInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").CommunityComment>>;
|
|
60
|
+
listOwnCommunityComments(input: SearchInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").CommunityComment>>;
|
|
61
|
+
getCommunityMessageCount(runId: string): Promise<number>;
|
|
62
|
+
listCommunityMessages(input: SearchInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").CommunityMessage>>;
|
|
63
|
+
createCommunityComment(input: CommunityCommentCreateInput, runId: string): Promise<import("./types.js").CommunityCommentCreateResult>;
|
|
64
|
+
deleteCommunityComment(commentSn: string, runId: string): Promise<boolean>;
|
|
65
|
+
searchMovies(input: SearchInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").MovieSummary>>;
|
|
66
|
+
getMovie(movieId: string, runId: string): Promise<import("./types.js").MovieDetail>;
|
|
67
|
+
searchGames(input: SearchInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").GameSummary>>;
|
|
68
|
+
getGame(gameId: string, runId: string): Promise<import("./types.js").GameDetail>;
|
|
69
|
+
searchArchive(input: ArchiveSearchInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").ArchiveItem>>;
|
|
70
|
+
getArchiveItem(resourceType: ArchiveResourceType, itemId: string, runId: string): Promise<import("./types.js").ArchiveItem>;
|
|
71
|
+
searchWiki(input: SearchInput, runId: string): Promise<unknown>;
|
|
72
|
+
getWiki(entryId: string, runId: string): Promise<unknown>;
|
|
73
|
+
createWikiContribution(input: WikiContributionWriteInput & {
|
|
74
|
+
resourceId: string;
|
|
75
|
+
operation: 'create' | 'update' | 'delete';
|
|
76
|
+
}, runId: string): Promise<unknown>;
|
|
77
|
+
updateWikiContribution(resourceId: string, input: WikiContributionWriteInput, runId: string): Promise<unknown>;
|
|
78
|
+
deleteWikiContribution(resourceId: string, runId: string): Promise<unknown>;
|
|
79
|
+
submitWikiContribution(resourceId: string, runId: string): Promise<unknown>;
|
|
80
|
+
withdrawWikiContribution(resourceId: string, runId: string): Promise<unknown>;
|
|
81
|
+
reopenWikiContribution(resourceId: string, runId: string): Promise<unknown>;
|
|
82
|
+
getWikiContribution(resourceId: string, runId: string): Promise<unknown>;
|
|
83
|
+
listWikiContributions(input: SearchInput, runId: string): Promise<unknown>;
|
|
84
|
+
getWikiContributionHistory(draftSn: string, runId: string): Promise<unknown>;
|
|
85
|
+
listBlogArticles(input: BlogListInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").BlogArticle>>;
|
|
86
|
+
listBlogHeadlines(runId: string): Promise<import("./types.js").BlogHeadline[]>;
|
|
87
|
+
getBlogArticle(articleId: string, runId: string): Promise<import("./types.js").BlogArticle>;
|
|
88
|
+
listOwnedBlogArticles(input: SearchInput, runId: string): Promise<import("./types.js").SearchResult<import("./types.js").OwnedBlogArticle>>;
|
|
89
|
+
createBlogDraft(input: BlogDraftCreateInput, runId: string): Promise<import("./types.js").BlogDraft>;
|
|
90
|
+
openBlogDraft(articleId: string, runId: string): Promise<import("./types.js").BlogDraft>;
|
|
91
|
+
updateBlogDraftContent(articleId: string, content: string, runId: string): Promise<import("./types.js").BlogDraftContent>;
|
|
92
|
+
private contributionAction;
|
|
93
|
+
private registerDevice;
|
|
94
|
+
private request;
|
|
95
|
+
private requestWithHeaders;
|
|
96
|
+
}
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { projectAccountContext, projectAssetOverview, projectArchiveCarousel, projectArchiveDetail, projectArchiveSearch, projectBlogDetail, projectBlogHeadlines, projectBlogDraft, projectBlogDraftContent, projectBlogList, projectCapabilities, projectCharacter, projectCharacterSearch, projectBooleanMutation, projectCommunityCommentCreate, projectCommunityComments, projectCommunityMessageCount, projectCommunityMessages, projectCommunityPost, projectCommunityPosts, projectDocumentDirectory, projectDocumentSpace, projectDocumentSpaces, projectGameDetail, projectGameSearch, projectMovieDetail, projectMovieSearch, projectMetadataFields, projectOwnedCharacters, projectOwnedAssets, projectPublicLinks, projectOwnedBlogArticles, projectRelationRoles, projectResourceMetadata, projectTag, projectTagAttribute, projectTagAttributes, projectTags, } from './projections.js';
|
|
3
|
+
export class ApiXError extends Error {
|
|
4
|
+
code;
|
|
5
|
+
retryable;
|
|
6
|
+
constructor(message, code, retryable) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.retryable = retryable;
|
|
10
|
+
this.name = 'ApiXError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class ApiXClient {
|
|
14
|
+
config;
|
|
15
|
+
stateStore;
|
|
16
|
+
fetchImpl;
|
|
17
|
+
deviceRegistration;
|
|
18
|
+
constructor(config, stateStore, fetchImpl = fetch) {
|
|
19
|
+
this.config = config;
|
|
20
|
+
this.stateStore = stateStore;
|
|
21
|
+
this.fetchImpl = fetchImpl;
|
|
22
|
+
}
|
|
23
|
+
async ensureDevice() {
|
|
24
|
+
const existing = await this.stateStore.read();
|
|
25
|
+
if (existing.deviceId)
|
|
26
|
+
return existing.deviceId;
|
|
27
|
+
if (!this.deviceRegistration) {
|
|
28
|
+
this.deviceRegistration = this.registerDevice().finally(() => {
|
|
29
|
+
this.deviceRegistration = undefined;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return this.deviceRegistration;
|
|
33
|
+
}
|
|
34
|
+
async hasSession() {
|
|
35
|
+
return Boolean((await this.stateStore.read()).sessionToken);
|
|
36
|
+
}
|
|
37
|
+
async exchangeAgentAuthorization(code, codeVerifier, redirectUri) {
|
|
38
|
+
const response = await this.request('/account/agent-authorizations/token', {
|
|
39
|
+
method: 'POST',
|
|
40
|
+
body: { code, codeVerifier, redirectUri },
|
|
41
|
+
includeDevice: true,
|
|
42
|
+
// 授权码已绑定当前设备,设备失效时必须重新开始浏览器授权。
|
|
43
|
+
retryInvalidDevice: false,
|
|
44
|
+
includeSession: false,
|
|
45
|
+
});
|
|
46
|
+
const parsed = parseAgentLoginResult(response);
|
|
47
|
+
await this.stateStore.update((state) => ({
|
|
48
|
+
...state,
|
|
49
|
+
sessionToken: parsed.tokenId,
|
|
50
|
+
userId: parsed.userId,
|
|
51
|
+
characterId: parsed.characterId,
|
|
52
|
+
}));
|
|
53
|
+
return { userId: parsed.userId, characterId: parsed.characterId };
|
|
54
|
+
}
|
|
55
|
+
async logout() {
|
|
56
|
+
const state = await this.stateStore.read();
|
|
57
|
+
if (!state.sessionToken)
|
|
58
|
+
return;
|
|
59
|
+
try {
|
|
60
|
+
await this.request('/account/logout', { method: 'POST', includeDevice: true, retryInvalidDevice: false });
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await this.stateStore.clearSession();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async getCapabilities(runId) {
|
|
67
|
+
return projectCapabilities(await this.request('/agent/capabilities', { runId, toolName: 'kloudy_get_capabilities' }));
|
|
68
|
+
}
|
|
69
|
+
async getAccountContext(runId) {
|
|
70
|
+
return projectAccountContext(await this.request('/account/', { runId, toolName: 'account_get_context' }));
|
|
71
|
+
}
|
|
72
|
+
async listOwnedCharacters(runId) {
|
|
73
|
+
return projectOwnedCharacters(await this.request('/account/characters', { runId, toolName: 'account_list_characters' }));
|
|
74
|
+
}
|
|
75
|
+
async getAssetOverview(runId) {
|
|
76
|
+
return projectAssetOverview(await this.request('/account/assets', { runId, toolName: 'account_get_asset_overview' }));
|
|
77
|
+
}
|
|
78
|
+
async listAssets(input, runId) {
|
|
79
|
+
const query = new URLSearchParams({ page: String(input.page ?? 1) });
|
|
80
|
+
return projectOwnedAssets(await this.request('/account/assets/assets', {
|
|
81
|
+
query,
|
|
82
|
+
runId,
|
|
83
|
+
toolName: 'account_list_assets',
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
async searchCharacters(input, runId) {
|
|
87
|
+
const query = paginationQuery(input);
|
|
88
|
+
if (input.characterId)
|
|
89
|
+
query.set('characterId', input.characterId);
|
|
90
|
+
if (input.name)
|
|
91
|
+
query.set('name', input.name);
|
|
92
|
+
return projectCharacterSearch(await this.request('/characters', { query, runId, toolName: 'characters_search' }));
|
|
93
|
+
}
|
|
94
|
+
async getCharacter(characterId, runId) {
|
|
95
|
+
return projectCharacter(await this.request(`/characters/${encodeURIComponent(characterId)}`, {
|
|
96
|
+
runId,
|
|
97
|
+
toolName: 'characters_get',
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
async getCharacterTags(characterId, runId) {
|
|
101
|
+
return projectTags(await this.request(`/characters/${encodeURIComponent(characterId)}/tags`, {
|
|
102
|
+
runId,
|
|
103
|
+
toolName: 'characters_get_tags',
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
async getCharacterMetadata(characterId, runId) {
|
|
107
|
+
return projectResourceMetadata(await this.request(`/characters/${encodeURIComponent(characterId)}/metadata`, {
|
|
108
|
+
runId,
|
|
109
|
+
toolName: 'characters_get_metadata',
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
async listTagAttributes(category, runId) {
|
|
113
|
+
return projectTagAttributes(await this.request('/tags/attributes', {
|
|
114
|
+
query: optionalFilterQuery('category', category),
|
|
115
|
+
runId,
|
|
116
|
+
toolName: 'tags_list_attributes',
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
async getTagAttribute(attributeId, runId) {
|
|
120
|
+
return projectTagAttribute(await this.request(`/tags/attributes/${encodeURIComponent(attributeId)}`, {
|
|
121
|
+
runId,
|
|
122
|
+
toolName: 'tags_get_attribute',
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
125
|
+
async listTags(attributeId, runId) {
|
|
126
|
+
return projectTags(await this.request('/tags', {
|
|
127
|
+
query: optionalFilterQuery('attr_id', attributeId),
|
|
128
|
+
runId,
|
|
129
|
+
toolName: 'tags_list',
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
async getTag(attributeId, tagId, runId) {
|
|
133
|
+
return projectTag(await this.request(`/tags/${encodeURIComponent(attributeId)}/${encodeURIComponent(tagId)}`, {
|
|
134
|
+
runId,
|
|
135
|
+
toolName: 'tags_get',
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
async listMetadataFields(category, runId) {
|
|
139
|
+
return projectMetadataFields(await this.request('/metadata/fields', {
|
|
140
|
+
query: optionalFilterQuery('category', category),
|
|
141
|
+
runId,
|
|
142
|
+
toolName: 'metadata_list_fields',
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
async listRelationRoles(runId) {
|
|
146
|
+
return projectRelationRoles(await this.request('/relations/roles', { runId, toolName: 'relations_list_roles' }));
|
|
147
|
+
}
|
|
148
|
+
async listLinks(classId, runId) {
|
|
149
|
+
const path = classId ? `/links/class/${encodeURIComponent(classId)}` : '/links/';
|
|
150
|
+
return projectPublicLinks(await this.request(path, { runId, toolName: 'links_list' }));
|
|
151
|
+
}
|
|
152
|
+
async listArchiveCarousel(runId) {
|
|
153
|
+
return projectArchiveCarousel(await this.request('/archive/carousel', { runId, toolName: 'archive_list_carousel' }));
|
|
154
|
+
}
|
|
155
|
+
async listDocumentSpaces(runId) {
|
|
156
|
+
return projectDocumentSpaces(await this.request('/documents/spaces', { runId, toolName: 'documents_list_spaces' }));
|
|
157
|
+
}
|
|
158
|
+
async getDocumentSpace(spaceId, runId) {
|
|
159
|
+
return projectDocumentSpace(await this.request(`/documents/spaces/${encodeURIComponent(spaceId)}`, {
|
|
160
|
+
runId,
|
|
161
|
+
toolName: 'documents_get_space',
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
async listDocumentDirectory(input, runId) {
|
|
165
|
+
const suffix = input.directoryPath ? `/${encodePathSegments(input.directoryPath)}` : '';
|
|
166
|
+
const projected = projectDocumentDirectory(await this.request(`/documents/spaces/${encodeURIComponent(input.spaceId)}/${input.variant}/${input.moduleId}/directory${suffix}`, { runId, toolName: 'documents_list_directory' }));
|
|
167
|
+
return { ...input, ...projected };
|
|
168
|
+
}
|
|
169
|
+
async readDocument(input, runId) {
|
|
170
|
+
const content = await this.request(`/documents/spaces/${encodeURIComponent(input.spaceId)}/${input.variant}/${input.moduleId}/file/${encodePathSegments(input.documentPath)}`, { runId, toolName: 'documents_read' });
|
|
171
|
+
if (typeof content !== 'string') {
|
|
172
|
+
throw new ApiXError('Kloudy document response was invalid', 'document_response_invalid', false);
|
|
173
|
+
}
|
|
174
|
+
return { ...input, content };
|
|
175
|
+
}
|
|
176
|
+
async listCommunityPosts(input, runId) {
|
|
177
|
+
return projectCommunityPosts(await this.request('/community/posts', {
|
|
178
|
+
query: legacyPaginationQuery(input),
|
|
179
|
+
runId,
|
|
180
|
+
toolName: 'community_list_posts',
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
async getCommunityPost(postId, runId) {
|
|
184
|
+
return projectCommunityPost(await this.request(`/community/posts/${encodePathSegments(postId)}`, {
|
|
185
|
+
runId,
|
|
186
|
+
toolName: 'community_get_post',
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
189
|
+
async listCommunityComments(postId, input, runId) {
|
|
190
|
+
const query = legacyPaginationQuery(input);
|
|
191
|
+
query.set('post_id', postId);
|
|
192
|
+
return projectCommunityComments(await this.request('/community/comments', { query, runId, toolName: 'community_list_comments' }));
|
|
193
|
+
}
|
|
194
|
+
async listOwnCommunityComments(input, runId) {
|
|
195
|
+
return projectCommunityComments(await this.request('/community/my-comments', {
|
|
196
|
+
query: legacyPaginationQuery(input),
|
|
197
|
+
runId,
|
|
198
|
+
toolName: 'community_list_my_comments',
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
async getCommunityMessageCount(runId) {
|
|
202
|
+
return projectCommunityMessageCount(await this.request('/community/messages/count', { runId, toolName: 'community_get_message_count' }));
|
|
203
|
+
}
|
|
204
|
+
async listCommunityMessages(input, runId) {
|
|
205
|
+
return projectCommunityMessages(await this.request('/community/messages', {
|
|
206
|
+
query: legacyPaginationQuery(input),
|
|
207
|
+
runId,
|
|
208
|
+
toolName: 'community_list_messages',
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
async createCommunityComment(input, runId) {
|
|
212
|
+
const body = input.target.type === 'post'
|
|
213
|
+
? { postId: input.target.postId, content: input.content }
|
|
214
|
+
: { parentCommentSn: input.target.parentCommentSn, content: input.content };
|
|
215
|
+
return projectCommunityCommentCreate(await this.request('/community/comments', {
|
|
216
|
+
method: 'POST',
|
|
217
|
+
body,
|
|
218
|
+
runId,
|
|
219
|
+
toolName: 'community_create_comment',
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
async deleteCommunityComment(commentSn, runId) {
|
|
223
|
+
const query = new URLSearchParams({ comment_sn: commentSn });
|
|
224
|
+
return projectBooleanMutation(await this.request('/community/comments', {
|
|
225
|
+
method: 'DELETE',
|
|
226
|
+
query,
|
|
227
|
+
runId,
|
|
228
|
+
toolName: 'community_delete_comment',
|
|
229
|
+
}));
|
|
230
|
+
}
|
|
231
|
+
async searchMovies(input, runId) {
|
|
232
|
+
const query = paginationQuery(input);
|
|
233
|
+
if (input.query)
|
|
234
|
+
query.set('title', input.query);
|
|
235
|
+
return projectMovieSearch(await this.request('/archive/movies', { query, runId, toolName: 'movies_search' }));
|
|
236
|
+
}
|
|
237
|
+
async getMovie(movieId, runId) {
|
|
238
|
+
return projectMovieDetail(await this.request(`/archive/movies/${encodeURIComponent(movieId)}`, { runId, toolName: 'movies_get' }));
|
|
239
|
+
}
|
|
240
|
+
async searchGames(input, runId) {
|
|
241
|
+
const query = paginationQuery(input);
|
|
242
|
+
if (input.query)
|
|
243
|
+
query.set('search', input.query);
|
|
244
|
+
return projectGameSearch(await this.request('/archive/games', { query, runId, toolName: 'games_search' }));
|
|
245
|
+
}
|
|
246
|
+
async getGame(gameId, runId) {
|
|
247
|
+
return projectGameDetail(await this.request(`/archive/games/${encodeURIComponent(gameId)}`, { runId, toolName: 'games_get' }));
|
|
248
|
+
}
|
|
249
|
+
async searchArchive(input, runId) {
|
|
250
|
+
const query = paginationQuery(input);
|
|
251
|
+
if (input.query)
|
|
252
|
+
query.set(archiveSearchParameter(input.resourceType), input.query);
|
|
253
|
+
return projectArchiveSearch(input.resourceType, await this.request(`/archive/${archiveRoute(input.resourceType)}`, {
|
|
254
|
+
query,
|
|
255
|
+
runId,
|
|
256
|
+
toolName: 'archive_search',
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
async getArchiveItem(resourceType, itemId, runId) {
|
|
260
|
+
return projectArchiveDetail(resourceType, await this.request(`/archive/${archiveRoute(resourceType)}/${encodeURIComponent(itemId)}`, {
|
|
261
|
+
runId,
|
|
262
|
+
toolName: 'archive_get',
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
async searchWiki(input, runId) {
|
|
266
|
+
if (!input.query)
|
|
267
|
+
throw new ApiXError('Wiki search text is required', 'wiki_query_required', false);
|
|
268
|
+
const query = paginationQuery(input);
|
|
269
|
+
query.set('query', input.query);
|
|
270
|
+
return this.request('/wiki/search', { query, runId, toolName: 'wiki_search' });
|
|
271
|
+
}
|
|
272
|
+
async getWiki(entryId, runId) {
|
|
273
|
+
return this.request(`/wiki/entries/${encodeWikiPath(entryId)}`, { runId, toolName: 'wiki_get' });
|
|
274
|
+
}
|
|
275
|
+
async createWikiContribution(input, runId) {
|
|
276
|
+
return this.request('/metadata-drafts', {
|
|
277
|
+
method: 'POST',
|
|
278
|
+
body: input,
|
|
279
|
+
runId,
|
|
280
|
+
toolName: 'wiki_contribution_create_draft',
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
async updateWikiContribution(resourceId, input, runId) {
|
|
284
|
+
return this.request(activeDraftPath('/metadata-drafts/active', resourceId), {
|
|
285
|
+
method: 'PUT',
|
|
286
|
+
body: input,
|
|
287
|
+
runId,
|
|
288
|
+
toolName: 'wiki_contribution_update_draft',
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async deleteWikiContribution(resourceId, runId) {
|
|
292
|
+
return this.request(activeDraftPath('/metadata-drafts/active', resourceId), {
|
|
293
|
+
method: 'DELETE',
|
|
294
|
+
runId,
|
|
295
|
+
toolName: 'wiki_contribution_delete_draft',
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
async submitWikiContribution(resourceId, runId) {
|
|
299
|
+
return this.contributionAction(resourceId, 'submit', runId, 'wiki_contribution_submit');
|
|
300
|
+
}
|
|
301
|
+
async withdrawWikiContribution(resourceId, runId) {
|
|
302
|
+
return this.contributionAction(resourceId, 'withdraw', runId, 'wiki_contribution_withdraw');
|
|
303
|
+
}
|
|
304
|
+
async reopenWikiContribution(resourceId, runId) {
|
|
305
|
+
return this.contributionAction(resourceId, 'reopen', runId, 'wiki_contribution_reopen');
|
|
306
|
+
}
|
|
307
|
+
async getWikiContribution(resourceId, runId) {
|
|
308
|
+
return this.request(activeDraftPath('/metadata-drafts/active', resourceId), {
|
|
309
|
+
runId,
|
|
310
|
+
toolName: 'wiki_contribution_status',
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
async listWikiContributions(input, runId) {
|
|
314
|
+
return this.request('/metadata-drafts/mine', {
|
|
315
|
+
query: paginationQuery(input),
|
|
316
|
+
runId,
|
|
317
|
+
toolName: 'wiki_contribution_list',
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
async getWikiContributionHistory(draftSn, runId) {
|
|
321
|
+
return this.request(`/metadata-drafts/${encodeURIComponent(draftSn)}`, {
|
|
322
|
+
runId,
|
|
323
|
+
toolName: 'wiki_contribution_history_get',
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
async listBlogArticles(input, runId) {
|
|
327
|
+
const query = new URLSearchParams();
|
|
328
|
+
query.set('page', String(input.page ?? 1));
|
|
329
|
+
query.set('pageSize', String(input.pageSize ?? 10));
|
|
330
|
+
if (input.category)
|
|
331
|
+
query.set('category', input.category);
|
|
332
|
+
if (input.characterId)
|
|
333
|
+
query.set('characterId', input.characterId);
|
|
334
|
+
return projectBlogList(await this.request('/blog/articles', { query, runId, toolName: 'blog_list_articles' }));
|
|
335
|
+
}
|
|
336
|
+
async listBlogHeadlines(runId) {
|
|
337
|
+
return projectBlogHeadlines(await this.request('/blog/headlines', { runId, toolName: 'blog_list_headlines' }));
|
|
338
|
+
}
|
|
339
|
+
async getBlogArticle(articleId, runId) {
|
|
340
|
+
return projectBlogDetail(await this.request(`/blog/articles/${encodePathSegments(articleId)}`, {
|
|
341
|
+
runId,
|
|
342
|
+
toolName: 'blog_read_article',
|
|
343
|
+
}));
|
|
344
|
+
}
|
|
345
|
+
async listOwnedBlogArticles(input, runId) {
|
|
346
|
+
return projectOwnedBlogArticles(await this.request('/blog/owned-articles', {
|
|
347
|
+
query: paginationQuery(input),
|
|
348
|
+
runId,
|
|
349
|
+
toolName: 'blog_list_owned_articles',
|
|
350
|
+
}));
|
|
351
|
+
}
|
|
352
|
+
async createBlogDraft(input, runId) {
|
|
353
|
+
return projectBlogDraft(await this.request('/documents/blog-drafts/create', {
|
|
354
|
+
method: 'POST',
|
|
355
|
+
body: input,
|
|
356
|
+
runId,
|
|
357
|
+
toolName: 'blog_draft_create',
|
|
358
|
+
}));
|
|
359
|
+
}
|
|
360
|
+
async openBlogDraft(articleId, runId) {
|
|
361
|
+
return projectBlogDraft(await this.request('/documents/blog-drafts/open', {
|
|
362
|
+
method: 'POST',
|
|
363
|
+
body: { articleId },
|
|
364
|
+
runId,
|
|
365
|
+
toolName: 'blog_draft_open',
|
|
366
|
+
}));
|
|
367
|
+
}
|
|
368
|
+
async updateBlogDraftContent(articleId, content, runId) {
|
|
369
|
+
return projectBlogDraftContent(await this.request('/documents/blog-drafts/content', {
|
|
370
|
+
method: 'PUT',
|
|
371
|
+
body: { articleId, content },
|
|
372
|
+
runId,
|
|
373
|
+
toolName: 'blog_draft_update_content',
|
|
374
|
+
}));
|
|
375
|
+
}
|
|
376
|
+
contributionAction(resourceId, action, runId, toolName) {
|
|
377
|
+
return this.request(activeDraftPath(`/metadata-drafts/active/${action}`, resourceId), {
|
|
378
|
+
method: 'POST',
|
|
379
|
+
runId,
|
|
380
|
+
toolName,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async registerDevice() {
|
|
384
|
+
const data = await this.request('/account/devices', {
|
|
385
|
+
method: 'POST',
|
|
386
|
+
includeDevice: false,
|
|
387
|
+
retryInvalidDevice: false,
|
|
388
|
+
});
|
|
389
|
+
const deviceId = typeof data === 'object' && data !== null && 'deviceId' in data ? data.deviceId : undefined;
|
|
390
|
+
if (typeof deviceId !== 'string' || deviceId.length === 0) {
|
|
391
|
+
throw new ApiXError('Kloudy device registration returned an invalid response', 'device_response_invalid', false);
|
|
392
|
+
}
|
|
393
|
+
await this.stateStore.update((state) => ({ ...state, deviceId }));
|
|
394
|
+
return deviceId;
|
|
395
|
+
}
|
|
396
|
+
async request(path, options = {}) {
|
|
397
|
+
return (await this.requestWithHeaders(path, options)).data;
|
|
398
|
+
}
|
|
399
|
+
async requestWithHeaders(path, options = {}) {
|
|
400
|
+
const includeDevice = options.includeDevice ?? true;
|
|
401
|
+
if (includeDevice)
|
|
402
|
+
await this.ensureDevice();
|
|
403
|
+
const state = await this.stateStore.read();
|
|
404
|
+
const url = new URL(`${this.config.apiXBaseUrl.pathname}${path}`, this.config.apiXBaseUrl.origin);
|
|
405
|
+
if (options.query)
|
|
406
|
+
url.search = options.query.toString();
|
|
407
|
+
const headers = new Headers({
|
|
408
|
+
Accept: 'application/json',
|
|
409
|
+
'User-Agent': 'kloudy-agent-server/0.1.1',
|
|
410
|
+
'X-Agent-Client': 'local-mcp',
|
|
411
|
+
'X-Agent-Run-ID': options.runId ?? randomUUID(),
|
|
412
|
+
});
|
|
413
|
+
if (options.toolName) {
|
|
414
|
+
headers.set('X-Agent-Tool', options.toolName);
|
|
415
|
+
headers.set('X-Agent-Tool-Call-ID', randomUUID());
|
|
416
|
+
}
|
|
417
|
+
if (includeDevice && state.deviceId)
|
|
418
|
+
headers.set('K-Device-ID', state.deviceId);
|
|
419
|
+
if (options.includeSession !== false && state.characterId)
|
|
420
|
+
headers.set('K-Character-ID', state.characterId);
|
|
421
|
+
if (options.includeSession !== false && state.sessionToken)
|
|
422
|
+
headers.set('Cookie', `k_token_id=${state.sessionToken}`);
|
|
423
|
+
if (options.body !== undefined)
|
|
424
|
+
headers.set('Content-Type', 'application/json');
|
|
425
|
+
const controller = new AbortController();
|
|
426
|
+
const timeout = setTimeout(() => controller.abort(), this.config.requestTimeoutMs);
|
|
427
|
+
let response;
|
|
428
|
+
try {
|
|
429
|
+
const requestInit = {
|
|
430
|
+
method: options.method ?? 'GET',
|
|
431
|
+
headers,
|
|
432
|
+
signal: controller.signal,
|
|
433
|
+
};
|
|
434
|
+
if (options.body !== undefined)
|
|
435
|
+
requestInit.body = JSON.stringify(options.body);
|
|
436
|
+
response = await this.fetchImpl(url, requestInit);
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
if (error.name === 'AbortError') {
|
|
440
|
+
throw new ApiXError('Kloudy API request timed out', 'api_timeout', true);
|
|
441
|
+
}
|
|
442
|
+
throw new ApiXError('Kloudy API is unavailable', 'api_unavailable', true);
|
|
443
|
+
}
|
|
444
|
+
finally {
|
|
445
|
+
clearTimeout(timeout);
|
|
446
|
+
}
|
|
447
|
+
const envelope = await parseEnvelope(response);
|
|
448
|
+
if (!response.ok || envelope.success === false) {
|
|
449
|
+
if (envelope.code === 'device_id_invalid' && includeDevice && options.retryInvalidDevice !== false) {
|
|
450
|
+
await this.stateStore.clearDevice();
|
|
451
|
+
await this.ensureDevice();
|
|
452
|
+
return this.requestWithHeaders(path, { ...options, retryInvalidDevice: false });
|
|
453
|
+
}
|
|
454
|
+
if (envelope.code === 'need_user_token' &&
|
|
455
|
+
options.includeSession !== false &&
|
|
456
|
+
Boolean(state.sessionToken)) {
|
|
457
|
+
// 上游已经明确拒绝当前 User Token。清理账号会话以允许重新授权,
|
|
458
|
+
// 但保留匿名设备,使公开查询仍可继续使用。
|
|
459
|
+
await this.stateStore.clearSession();
|
|
460
|
+
}
|
|
461
|
+
throw mapApiError(response.status, envelope);
|
|
462
|
+
}
|
|
463
|
+
if (envelope.success !== true || !('data' in envelope)) {
|
|
464
|
+
throw new ApiXError('Kloudy API returned an invalid response', 'api_response_invalid', false);
|
|
465
|
+
}
|
|
466
|
+
return { data: envelope.data, headers: response.headers };
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function encodeWikiPath(entryId) {
|
|
470
|
+
return encodePathSegments(entryId);
|
|
471
|
+
}
|
|
472
|
+
function encodePathSegments(value) {
|
|
473
|
+
return value
|
|
474
|
+
.split('/')
|
|
475
|
+
.map((part) => encodeURIComponent(part))
|
|
476
|
+
.join('/');
|
|
477
|
+
}
|
|
478
|
+
function archiveRoute(resourceType) {
|
|
479
|
+
return {
|
|
480
|
+
movie: 'movies',
|
|
481
|
+
game: 'games',
|
|
482
|
+
song: 'songs',
|
|
483
|
+
activity: 'activities',
|
|
484
|
+
product: 'products',
|
|
485
|
+
}[resourceType];
|
|
486
|
+
}
|
|
487
|
+
function archiveSearchParameter(resourceType) {
|
|
488
|
+
return resourceType === 'game' ? 'search' : resourceType === 'activity' ? 'search' : 'title';
|
|
489
|
+
}
|
|
490
|
+
function activeDraftPath(path, resourceId) {
|
|
491
|
+
const query = new URLSearchParams({ resourceType: 'wiki', resourceId });
|
|
492
|
+
return `${path}?${query.toString()}`;
|
|
493
|
+
}
|
|
494
|
+
function paginationQuery(input) {
|
|
495
|
+
const query = new URLSearchParams();
|
|
496
|
+
query.set('page', String(input.page ?? 1));
|
|
497
|
+
query.set('pageSize', String(input.pageSize ?? 10));
|
|
498
|
+
return query;
|
|
499
|
+
}
|
|
500
|
+
function legacyPaginationQuery(input) {
|
|
501
|
+
const query = new URLSearchParams();
|
|
502
|
+
query.set('current_page', String(input.page ?? 1));
|
|
503
|
+
query.set('page_size', String(input.pageSize ?? 10));
|
|
504
|
+
return query;
|
|
505
|
+
}
|
|
506
|
+
function optionalFilterQuery(name, value) {
|
|
507
|
+
const query = new URLSearchParams();
|
|
508
|
+
if (value)
|
|
509
|
+
query.set(name, value);
|
|
510
|
+
return query;
|
|
511
|
+
}
|
|
512
|
+
async function parseEnvelope(response) {
|
|
513
|
+
try {
|
|
514
|
+
const value = await response.json();
|
|
515
|
+
return typeof value === 'object' && value !== null ? value : {};
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
return {};
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
function mapApiError(status, envelope) {
|
|
522
|
+
const upstreamCode = typeof envelope.code === 'string' ? envelope.code : undefined;
|
|
523
|
+
if (status === 401 || status === 403 || envelope.error === 'UNAUTHORIZED') {
|
|
524
|
+
return new ApiXError('Kloudy API denied this request', upstreamCode ?? 'permission_denied', false);
|
|
525
|
+
}
|
|
526
|
+
if (status >= 500)
|
|
527
|
+
return new ApiXError('Kloudy API failed to process the request', upstreamCode ?? 'api_server_error', true);
|
|
528
|
+
if (upstreamCode === 'id_not_found')
|
|
529
|
+
return new ApiXError('Kloudy archive item was not found', upstreamCode, false);
|
|
530
|
+
return new ApiXError('Kloudy API rejected the request', upstreamCode ?? 'api_request_rejected', false);
|
|
531
|
+
}
|
|
532
|
+
function parseAgentLoginResult(value) {
|
|
533
|
+
if (!isRecord(value) || typeof value.tokenId !== 'string' || !isRecord(value.user) || !isRecord(value.character)) {
|
|
534
|
+
throw new ApiXError('Kloudy authorization returned an invalid response', 'login_response_invalid', false);
|
|
535
|
+
}
|
|
536
|
+
const userId = value.user.userId;
|
|
537
|
+
const characterId = value.character.characterId;
|
|
538
|
+
if (typeof userId !== 'string' ||
|
|
539
|
+
typeof characterId !== 'string' ||
|
|
540
|
+
value.character.characterType !== 'agent' ||
|
|
541
|
+
value.tokenId.length === 0) {
|
|
542
|
+
throw new ApiXError('Kloudy authorization did not return an Agent Character', 'login_response_invalid', false);
|
|
543
|
+
}
|
|
544
|
+
return { tokenId: value.tokenId, userId, characterId };
|
|
545
|
+
}
|
|
546
|
+
function isRecord(value) {
|
|
547
|
+
return typeof value === 'object' && value !== null;
|
|
548
|
+
}
|
|
549
|
+
//# sourceMappingURL=client.js.map
|