@mokoconsulting/mokoabs-mcp 0.0.0-stage → 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +41 -0
- package/.gitattributes +94 -0
- package/.gitmessage +9 -0
- package/.mokogit/ISSUE_TEMPLATE/adr.md +110 -0
- package/.mokogit/ISSUE_TEMPLATE/bug_report.md +48 -0
- package/.mokogit/ISSUE_TEMPLATE/config.yml +18 -0
- package/.mokogit/ISSUE_TEMPLATE/documentation.md +52 -0
- package/.mokogit/ISSUE_TEMPLATE/feature_request.md +51 -0
- package/.mokogit/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
- package/.mokogit/ISSUE_TEMPLATE/mcp_connection_issue.md +69 -0
- package/.mokogit/ISSUE_TEMPLATE/mcp_tool_request.md +50 -0
- package/.mokogit/ISSUE_TEMPLATE/question.md +82 -0
- package/.mokogit/ISSUE_TEMPLATE/rfc.md +126 -0
- package/.mokogit/ISSUE_TEMPLATE/security.md +51 -0
- package/.mokogit/ISSUE_TEMPLATE/version.md +24 -0
- package/.mokogit/actions/resolve-source-dir/action.yml +72 -0
- package/.mokogit/workflows/apidocs.yml +132 -0
- package/.mokogit/workflows/auto-assign.yml +76 -0
- package/.mokogit/workflows/auto-dev-issue.yml +207 -0
- package/.mokogit/workflows/auto-release.yml +1787 -0
- package/.mokogit/workflows/branch-cleanup.yml +63 -0
- package/.mokogit/workflows/cascade.yml +645 -0
- package/.mokogit/workflows/ci-issue-reporter.yml +100 -0
- package/.mokogit/workflows/cleanup.yml +112 -0
- package/.mokogit/workflows/makefile-lint.yml +240 -0
- package/.mokogit/workflows/npm-build-test.yml +507 -0
- package/.mokogit/workflows/npm-sdk-check.yml +110 -0
- package/.mokogit/workflows/npm-tool-inventory.yml +81 -0
- package/.mokogit/workflows/pr-branch-check.yml +92 -0
- package/.mokogit/workflows/pre-release.yml +1134 -0
- package/.mokogit/workflows/rc-revert.yml +75 -0
- package/AGENTS.md +48 -0
- package/CHANGELOG.md +37 -0
- package/CODE_OF_CONDUCT.md +70 -0
- package/CONTRIBUTING.md +164 -0
- package/LICENSE +696 -0
- package/Makefile +94 -0
- package/README.md +109 -2
- package/SECURITY.md +245 -0
- package/build/README.md +7 -0
- package/dist/README.md +7 -0
- package/dist/index.js +153 -0
- package/package.json +34 -5
- package/sample/config.example.json +15 -0
- package/source/client.ts +132 -0
- package/source/config.ts +139 -0
- package/source/credentialStore.ts +216 -0
- package/source/index.ts +235 -0
- package/source/tokenReader.ts +120 -0
- package/source/types.ts +63 -0
- package/tsconfig.json +19 -0
package/source/index.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
3
|
+
*
|
|
4
|
+
* This file is part of a Moko Consulting project.
|
|
5
|
+
*
|
|
6
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
7
|
+
*
|
|
8
|
+
* FILE INFORMATION
|
|
9
|
+
* DEFGROUP: mokoabs-mcp.Server
|
|
10
|
+
* INGROUP: mokoabs-mcp
|
|
11
|
+
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoabs-mcp
|
|
12
|
+
* PATH: /source/index.ts
|
|
13
|
+
* VERSION: 01.00.00
|
|
14
|
+
* BRIEF: MCP server entry point — registers all Audiobookshelf API tools
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
18
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { loadConfig, getConnection } from './config.js';
|
|
21
|
+
import { ApiClient } from './client.js';
|
|
22
|
+
import type { ApiConfig, ApiResponse } from './types.js';
|
|
23
|
+
|
|
24
|
+
let config: ApiConfig;
|
|
25
|
+
|
|
26
|
+
function clientFor(connection?: string): ApiClient {
|
|
27
|
+
return new ApiClient(getConnection(config, connection));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatResponse(res: ApiResponse): { content: Array<{ type: 'text'; text: string }> } {
|
|
31
|
+
if (res.status >= 400) {
|
|
32
|
+
return {
|
|
33
|
+
content: [{ type: 'text' as const, text: `Error: HTTP ${res.status}: ${JSON.stringify(res.data, null, 2)}` }],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
content: [{ type: 'text' as const, text: JSON.stringify(res.data, null, 2) }],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Shared parameter definitions ────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const ConnectionParam = {
|
|
44
|
+
connection: z.string().optional().describe('Named connection from config (uses default if omitted)'),
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const PaginationParams = {
|
|
48
|
+
limit: z.number().optional().describe('Max results'),
|
|
49
|
+
page: z.number().optional().describe('Page number (0-based)'),
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function paginationQuery(params: { limit?: number; page?: number }): Record<string, string> {
|
|
53
|
+
const q: Record<string, string> = {};
|
|
54
|
+
if (params.limit !== undefined) q['limit'] = String(params.limit);
|
|
55
|
+
if (params.page !== undefined) q['page'] = String(params.page);
|
|
56
|
+
return q;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── Server ──────────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
const server = new McpServer({
|
|
62
|
+
name: 'mokoabs-mcp',
|
|
63
|
+
version: '1.0.0',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
67
|
+
// Audiobookshelf tools. The client prepends the "/api" prefix and a Bearer
|
|
68
|
+
// Authorization header, so endpoints below are written without "/api".
|
|
69
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
70
|
+
|
|
71
|
+
// ── Libraries ────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
server.tool(
|
|
74
|
+
'abs_libraries_list',
|
|
75
|
+
'List all Audiobookshelf libraries (id, name, mediaType).',
|
|
76
|
+
{ ...ConnectionParam },
|
|
77
|
+
async ({ connection }) => formatResponse(await clientFor(connection).get('/libraries')),
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
server.tool(
|
|
81
|
+
'abs_library_items',
|
|
82
|
+
'List items in a library (paginated). Optional ABS sort/desc/filter.',
|
|
83
|
+
{
|
|
84
|
+
libraryId: z.string().describe('Library ID'),
|
|
85
|
+
sort: z.string().optional().describe('Sort field, e.g. "media.metadata.title"'),
|
|
86
|
+
desc: z.boolean().optional().describe('Sort descending'),
|
|
87
|
+
filter: z.string().optional().describe('ABS filter string, e.g. "genres.<base64>"'),
|
|
88
|
+
...PaginationParams,
|
|
89
|
+
...ConnectionParam,
|
|
90
|
+
},
|
|
91
|
+
async ({ libraryId, sort, desc, filter, limit, page, connection }) => {
|
|
92
|
+
const params: Record<string, string> = { ...paginationQuery({ limit, page }) };
|
|
93
|
+
if (sort) params['sort'] = sort;
|
|
94
|
+
if (desc !== undefined) params['desc'] = desc ? '1' : '0';
|
|
95
|
+
if (filter) params['filter'] = filter;
|
|
96
|
+
return formatResponse(await clientFor(connection).get(`/libraries/${libraryId}/items`, params));
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
server.tool(
|
|
101
|
+
'abs_library_search',
|
|
102
|
+
'Search a library (books, series, authors, narrators, tags) by query.',
|
|
103
|
+
{
|
|
104
|
+
libraryId: z.string().describe('Library ID'),
|
|
105
|
+
q: z.string().describe('Search query'),
|
|
106
|
+
limit: z.number().optional().describe('Max results per group'),
|
|
107
|
+
...ConnectionParam,
|
|
108
|
+
},
|
|
109
|
+
async ({ libraryId, q, limit, connection }) => {
|
|
110
|
+
const params: Record<string, string> = { q };
|
|
111
|
+
if (limit !== undefined) params['limit'] = String(limit);
|
|
112
|
+
return formatResponse(await clientFor(connection).get(`/libraries/${libraryId}/search`, params));
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
server.tool(
|
|
117
|
+
'abs_library_scan',
|
|
118
|
+
'Trigger a scan of a library (picks up newly added/removed files).',
|
|
119
|
+
{
|
|
120
|
+
libraryId: z.string().describe('Library ID'),
|
|
121
|
+
force: z.boolean().optional().describe('Force a full re-scan of all items'),
|
|
122
|
+
...ConnectionParam,
|
|
123
|
+
},
|
|
124
|
+
async ({ libraryId, force, connection }) =>
|
|
125
|
+
formatResponse(await clientFor(connection).post(`/libraries/${libraryId}/scan${force ? '?force=1' : ''}`)),
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
server.tool(
|
|
129
|
+
'abs_library_collections',
|
|
130
|
+
'List collections in a library.',
|
|
131
|
+
{ libraryId: z.string().describe('Library ID'), ...ConnectionParam },
|
|
132
|
+
async ({ libraryId, connection }) =>
|
|
133
|
+
formatResponse(await clientFor(connection).get(`/libraries/${libraryId}/collections`)),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
server.tool(
|
|
137
|
+
'abs_library_series',
|
|
138
|
+
'List series in a library (paginated).',
|
|
139
|
+
{ libraryId: z.string().describe('Library ID'), ...PaginationParams, ...ConnectionParam },
|
|
140
|
+
async ({ libraryId, limit, page, connection }) =>
|
|
141
|
+
formatResponse(await clientFor(connection).get(`/libraries/${libraryId}/series`, paginationQuery({ limit, page }))),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
server.tool(
|
|
145
|
+
'abs_library_authors',
|
|
146
|
+
'List authors in a library.',
|
|
147
|
+
{ libraryId: z.string().describe('Library ID'), ...ConnectionParam },
|
|
148
|
+
async ({ libraryId, connection }) =>
|
|
149
|
+
formatResponse(await clientFor(connection).get(`/libraries/${libraryId}/authors`)),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// ── Items ────────────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
server.tool(
|
|
155
|
+
'abs_item_get',
|
|
156
|
+
'Get a library item by ID (optionally with expanded media/metadata).',
|
|
157
|
+
{
|
|
158
|
+
itemId: z.string().describe('Library item ID'),
|
|
159
|
+
expanded: z.boolean().optional().describe('Include full media/metadata'),
|
|
160
|
+
...ConnectionParam,
|
|
161
|
+
},
|
|
162
|
+
async ({ itemId, expanded, connection }) => {
|
|
163
|
+
const params: Record<string, string> = {};
|
|
164
|
+
if (expanded) params['expanded'] = '1';
|
|
165
|
+
return formatResponse(await clientFor(connection).get(`/items/${itemId}`, params));
|
|
166
|
+
},
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// ── Account ──────────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
server.tool(
|
|
172
|
+
'abs_me',
|
|
173
|
+
'Get the authenticated user (from the API key).',
|
|
174
|
+
{ ...ConnectionParam },
|
|
175
|
+
async ({ connection }) => formatResponse(await clientFor(connection).get('/me')),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
// ── Generic API Call ────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
server.tool(
|
|
181
|
+
'api_request',
|
|
182
|
+
'Make a raw Audiobookshelf API request to any endpoint (path is relative to /api).',
|
|
183
|
+
{
|
|
184
|
+
method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).describe('HTTP method'),
|
|
185
|
+
endpoint: z.string().describe('API endpoint path relative to /api (e.g. "/libraries")'),
|
|
186
|
+
body: z.record(z.string(), z.unknown()).optional().describe('Request body for POST/PUT/PATCH'),
|
|
187
|
+
params: z.record(z.string(), z.string()).optional().describe('Query parameters'),
|
|
188
|
+
...ConnectionParam,
|
|
189
|
+
},
|
|
190
|
+
async ({ method, endpoint, body, params, connection }) => {
|
|
191
|
+
const client = clientFor(connection);
|
|
192
|
+
switch (method) {
|
|
193
|
+
case 'GET':
|
|
194
|
+
return formatResponse(await client.get(endpoint, params));
|
|
195
|
+
case 'POST':
|
|
196
|
+
return formatResponse(await client.post(endpoint, body));
|
|
197
|
+
case 'PUT':
|
|
198
|
+
return formatResponse(await client.put(endpoint, body));
|
|
199
|
+
case 'PATCH':
|
|
200
|
+
return formatResponse(await client.patch(endpoint, body));
|
|
201
|
+
case 'DELETE':
|
|
202
|
+
return formatResponse(await client.delete(endpoint));
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// ── Connections Management ──────────────────────────────────────────────
|
|
208
|
+
|
|
209
|
+
server.tool(
|
|
210
|
+
'list_connections',
|
|
211
|
+
'List configured Audiobookshelf connections.',
|
|
212
|
+
{},
|
|
213
|
+
async () => {
|
|
214
|
+
const lines = Object.entries(config.connections).map(([name, conn]) => {
|
|
215
|
+
const is_default = name === config.defaultConnection ? ' (default)' : '';
|
|
216
|
+
return ` ${name}${is_default}: ${conn.baseUrl}`;
|
|
217
|
+
});
|
|
218
|
+
return {
|
|
219
|
+
content: [{ type: 'text' as const, text: `Configured connections:\n${lines.join('\n')}` }],
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// ── Start Server ────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
async function main(): Promise<void> {
|
|
227
|
+
config = await loadConfig();
|
|
228
|
+
const transport = new StdioServerTransport();
|
|
229
|
+
await server.connect(transport);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
main().catch((err) => {
|
|
233
|
+
process.stderr.write(`Fatal: ${err}\n`);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
});
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
*
|
|
3
|
+
* This file is part of a Moko Consulting project.
|
|
4
|
+
*
|
|
5
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
6
|
+
*
|
|
7
|
+
* FILE INFORMATION
|
|
8
|
+
* DEFGROUP: mokoabs-mcp.TokenReader
|
|
9
|
+
* INGROUP: mokoabs-mcp
|
|
10
|
+
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoabs-mcp
|
|
11
|
+
* PATH: /source/tokenReader.ts
|
|
12
|
+
* VERSION: 01.00.00
|
|
13
|
+
* BRIEF: MokoCLI #1043 — JSON-aware credential/token reader (vendored per-repo).
|
|
14
|
+
*
|
|
15
|
+
* GitHub standard primary; Google secondary = DETECT + WARN (no exchange).
|
|
16
|
+
* Secret values are NEVER written to stdout and NEVER echoed in errors/warnings.
|
|
17
|
+
*
|
|
18
|
+
* Detection ladder (resolveTokenValue):
|
|
19
|
+
* 1. trim; empty -> throw (no silent anonymous request).
|
|
20
|
+
* 2. JSON.parse; parse-fail OR non-object(array/null/scalar) -> the trimmed
|
|
21
|
+
* string IS the bearer (bare PAT, `gh auth token` output). [raw]
|
|
22
|
+
* 3. object .type in {service_account, authorized_user, external_account,
|
|
23
|
+
* impersonated_service_account} OR (.refresh_token && .token_uri)
|
|
24
|
+
* -> GOOGLE: emit a stderr WARNING and return an UNUSABLE result
|
|
25
|
+
* (bearer '', needsExchange true). Never bearer a private key.
|
|
26
|
+
* 4. else .token || .access_token -> bearer (+ expiry/scope/permissions). [github-json]
|
|
27
|
+
* 5. else -> throw (unrecognized object; never leak the blob as a token).
|
|
28
|
+
*/
|
|
29
|
+
import { readFile } from 'node:fs/promises';
|
|
30
|
+
import { homedir } from 'node:os';
|
|
31
|
+
import { resolve } from 'node:path';
|
|
32
|
+
|
|
33
|
+
export type TokenKind = 'raw' | 'github-json' | 'google';
|
|
34
|
+
|
|
35
|
+
export interface ResolvedToken {
|
|
36
|
+
/** Bearer value to send. '' when unusable (Google needs-exchange). */
|
|
37
|
+
bearer: string;
|
|
38
|
+
kind: TokenKind;
|
|
39
|
+
/** true for Google creds — not usable as a bearer by this server. */
|
|
40
|
+
needsExchange?: boolean;
|
|
41
|
+
expiresAt?: string;
|
|
42
|
+
expiresIn?: number;
|
|
43
|
+
scopes?: string[];
|
|
44
|
+
permissions?: Record<string, unknown>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const GOOGLE_TYPES = new Set([
|
|
48
|
+
'service_account',
|
|
49
|
+
'authorized_user',
|
|
50
|
+
'external_account',
|
|
51
|
+
'impersonated_service_account',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
/** Expand a leading "~" to $HOME, then resolve to an absolute path. */
|
|
55
|
+
export function expandHome(p: string): string {
|
|
56
|
+
if (p === '~') return homedir();
|
|
57
|
+
if (p.startsWith('~/') || p.startsWith('~\\')) return resolve(homedir(), p.slice(2));
|
|
58
|
+
return resolve(p);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** stderr ONLY — must never touch stdout (the MCP stdio channel). Never prints secret values. */
|
|
62
|
+
function warn(msg: string): void {
|
|
63
|
+
process.stderr.write(`[token] WARNING: ${msg}\n`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve RAW credential content into a bearer per MokoCLI #1043.
|
|
68
|
+
* `source` only labels errors/warnings; secret values are never echoed.
|
|
69
|
+
*/
|
|
70
|
+
export function resolveTokenValue(raw: string, source = 'credential'): ResolvedToken {
|
|
71
|
+
const trimmed = (raw ?? '').trim();
|
|
72
|
+
if (!trimmed) {
|
|
73
|
+
throw new Error(`Credential "${source}" is empty — refusing an anonymous request (set the token or remove the connection).`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let obj: unknown;
|
|
77
|
+
try {
|
|
78
|
+
obj = JSON.parse(trimmed);
|
|
79
|
+
} catch {
|
|
80
|
+
return { bearer: trimmed, kind: 'raw' }; // bare PAT / `gh auth token`
|
|
81
|
+
}
|
|
82
|
+
if (obj === null || typeof obj !== 'object' || Array.isArray(obj)) {
|
|
83
|
+
return { bearer: trimmed, kind: 'raw' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const o = obj as Record<string, unknown>;
|
|
87
|
+
const type = typeof o.type === 'string' ? o.type : '';
|
|
88
|
+
const isGoogle = GOOGLE_TYPES.has(type) || (typeof o.refresh_token === 'string' && typeof o.token_uri === 'string');
|
|
89
|
+
if (isGoogle) {
|
|
90
|
+
warn(`credential "${source}" is a Google ${type || 'OAuth token-cache'} JSON; this server does not perform Google token exchange, so it will NOT be used (a private key is never sent as a bearer token).`);
|
|
91
|
+
return { bearer: '', kind: 'google', needsExchange: true };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const bearer = typeof o.token === 'string' ? o.token : typeof o.access_token === 'string' ? o.access_token : '';
|
|
95
|
+
if (bearer) {
|
|
96
|
+
const scopes = Array.isArray(o.scopes)
|
|
97
|
+
? o.scopes.map(String)
|
|
98
|
+
: typeof o.scope === 'string'
|
|
99
|
+
? o.scope.split(/\s+/).filter(Boolean)
|
|
100
|
+
: undefined;
|
|
101
|
+
const expiresAt = typeof o.expires_at === 'string' ? o.expires_at : typeof o.expiry === 'string' ? o.expiry : undefined;
|
|
102
|
+
const expiresIn = typeof o.expires_in === 'number' ? o.expires_in : undefined;
|
|
103
|
+
const permissions = o.permissions && typeof o.permissions === 'object' ? (o.permissions as Record<string, unknown>) : undefined;
|
|
104
|
+
return { bearer, kind: 'github-json', expiresAt, expiresIn, scopes, permissions };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
throw new Error(`Credential "${source}" is an unrecognized JSON object (no "token"/"access_token") — refusing to use it as a bearer token.`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Read a token file (~ expands) and resolve it per #1043. */
|
|
111
|
+
export async function readTokenFile(path: string, source?: string): Promise<ResolvedToken> {
|
|
112
|
+
const abs = expandHome(path);
|
|
113
|
+
let raw: string;
|
|
114
|
+
try {
|
|
115
|
+
raw = await readFile(abs, 'utf-8');
|
|
116
|
+
} catch (err) {
|
|
117
|
+
throw new Error(`Failed to read token file "${abs}": ${err instanceof Error ? err.message : String(err)}`);
|
|
118
|
+
}
|
|
119
|
+
return resolveTokenValue(raw, source ?? abs);
|
|
120
|
+
}
|
package/source/types.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
2
|
+
*
|
|
3
|
+
* This file is part of a Moko Consulting project.
|
|
4
|
+
*
|
|
5
|
+
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
6
|
+
*
|
|
7
|
+
* FILE INFORMATION
|
|
8
|
+
* DEFGROUP: mokoabs-mcp.Types
|
|
9
|
+
* INGROUP: mokoabs-mcp
|
|
10
|
+
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokoabs-mcp
|
|
11
|
+
* PATH: /source/types.ts
|
|
12
|
+
* VERSION: 01.00.00
|
|
13
|
+
* BRIEF: TypeScript type definitions for Audiobookshelf MCP server
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Connection configuration for a single API instance.
|
|
18
|
+
*
|
|
19
|
+
* Rename and extend these fields to match your target API's auth mechanism:
|
|
20
|
+
* - `apiKey` + DOLAPIKEY header (Dolibarr)
|
|
21
|
+
* - `apiToken` + Bearer header (Joomla, GitHub)
|
|
22
|
+
* - `username`/`password` (Basic auth)
|
|
23
|
+
* - `oauth` fields (OAuth2 flows)
|
|
24
|
+
*/
|
|
25
|
+
export interface ApiConnection {
|
|
26
|
+
/** Base URL of the API instance (no trailing slash) */
|
|
27
|
+
baseUrl: string;
|
|
28
|
+
/**
|
|
29
|
+
* API key or token for authentication. Either a raw bearer string (passes
|
|
30
|
+
* through unchanged) or a JSON credential blob (GitHub token/access_token,
|
|
31
|
+
* or a Google credential which is detected and refused) per MokoCLI #1043.
|
|
32
|
+
* Optional when `tokenFile` is supplied instead.
|
|
33
|
+
*/
|
|
34
|
+
apiKey?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Path to a file holding the credential (~ expands). Read and resolved per
|
|
37
|
+
* MokoCLI #1043; takes precedence over inline `apiKey` when both are set.
|
|
38
|
+
*/
|
|
39
|
+
tokenFile?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Resolved bearer value used for `Authorization: Bearer <bearer>`. Populated
|
|
42
|
+
* by loadConfig() from `apiKey`/`tokenFile`; not provided by users directly.
|
|
43
|
+
*/
|
|
44
|
+
bearer?: string;
|
|
45
|
+
/** Skip TLS certificate verification (self-signed certs) */
|
|
46
|
+
insecure?: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Top-level configuration supporting multiple named connections.
|
|
51
|
+
*/
|
|
52
|
+
export interface ApiConfig {
|
|
53
|
+
connections: Record<string, ApiConnection>;
|
|
54
|
+
defaultConnection: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Normalized API response returned by the HTTP client.
|
|
59
|
+
*/
|
|
60
|
+
export interface ApiResponse {
|
|
61
|
+
status: number;
|
|
62
|
+
data: unknown;
|
|
63
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "Node16",
|
|
5
|
+
"moduleResolution": "Node16",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./source",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"declarationMap": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["source/**/*"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|