@fuul/mcp-server 0.2.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 +218 -0
- package/dist/affiliate-portal/affiliate-portal-queries.d.ts +15 -0
- package/dist/affiliate-portal/affiliate-portal-queries.d.ts.map +1 -0
- package/dist/affiliate-portal/affiliate-portal-queries.js +35 -0
- package/dist/affiliate-portal/affiliate-portal-queries.js.map +1 -0
- package/dist/agent/write-confirmation.d.ts +20 -0
- package/dist/agent/write-confirmation.d.ts.map +1 -0
- package/dist/agent/write-confirmation.js +34 -0
- package/dist/agent/write-confirmation.js.map +1 -0
- package/dist/auth/oauth-client.d.ts +13 -0
- package/dist/auth/oauth-client.d.ts.map +1 -0
- package/dist/auth/oauth-client.js +185 -0
- package/dist/auth/oauth-client.js.map +1 -0
- package/dist/auth/pkce.d.ts +5 -0
- package/dist/auth/pkce.d.ts.map +1 -0
- package/dist/auth/pkce.js +12 -0
- package/dist/auth/pkce.js.map +1 -0
- package/dist/auth/token-store.d.ts +7 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +44 -0
- package/dist/auth/token-store.js.map +1 -0
- package/dist/auth/types.d.ts +13 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +67 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/env.d.ts +10 -0
- package/dist/config/env.d.ts.map +1 -0
- package/dist/config/env.js +30 -0
- package/dist/config/env.js.map +1 -0
- package/dist/http/fuul-api-client.d.ts +51 -0
- package/dist/http/fuul-api-client.d.ts.map +1 -0
- package/dist/http/fuul-api-client.js +161 -0
- package/dist/http/fuul-api-client.js.map +1 -0
- package/dist/http/nest-query.d.ts +6 -0
- package/dist/http/nest-query.d.ts.map +1 -0
- package/dist/http/nest-query.js +24 -0
- package/dist/http/nest-query.js.map +1 -0
- package/dist/http/retry-after.d.ts +8 -0
- package/dist/http/retry-after.d.ts.map +1 -0
- package/dist/http/retry-after.js +28 -0
- package/dist/http/retry-after.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +366 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata/metadata-service.d.ts +11 -0
- package/dist/metadata/metadata-service.d.ts.map +1 -0
- package/dist/metadata/metadata-service.js +71 -0
- package/dist/metadata/metadata-service.js.map +1 -0
- package/dist/payouts/payout-batch-handlers.d.ts +4 -0
- package/dist/payouts/payout-batch-handlers.d.ts.map +1 -0
- package/dist/payouts/payout-batch-handlers.js +34 -0
- package/dist/payouts/payout-batch-handlers.js.map +1 -0
- package/dist/tools/tool-descriptions.d.ts +25 -0
- package/dist/tools/tool-descriptions.d.ts.map +1 -0
- package/dist/tools/tool-descriptions.js +40 -0
- package/dist/tools/tool-descriptions.js.map +1 -0
- package/dist/tools/tool-schemas.d.ts +35 -0
- package/dist/tools/tool-schemas.d.ts.map +1 -0
- package/dist/tools/tool-schemas.js +145 -0
- package/dist/tools/tool-schemas.js.map +1 -0
- package/dist/util/compact-query.d.ts +3 -0
- package/dist/util/compact-query.d.ts.map +1 -0
- package/dist/util/compact-query.js +5 -0
- package/dist/util/compact-query.js.map +1 -0
- package/dist/util/with-timeout.d.ts +12 -0
- package/dist/util/with-timeout.d.ts.map +1 -0
- package/dist/util/with-timeout.js +35 -0
- package/dist/util/with-timeout.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface StoredTokens {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token: string;
|
|
4
|
+
/** Epoch milliseconds when access_token is expected to expire. */
|
|
5
|
+
expires_at_ms: number;
|
|
6
|
+
}
|
|
7
|
+
export interface TokenResponse {
|
|
8
|
+
access_token: string;
|
|
9
|
+
refresh_token: string;
|
|
10
|
+
token_type: 'Bearer';
|
|
11
|
+
expires_in: number;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,QAAQ,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":""}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { OAuthClient } from './auth/oauth-client.js';
|
|
3
|
+
import { TokenStore } from './auth/token-store.js';
|
|
4
|
+
import { loadEnv } from './config/env.js';
|
|
5
|
+
import { ApiRequestError, FuulApiClient, NotLoggedInError } from './http/fuul-api-client.js';
|
|
6
|
+
function printUsage() {
|
|
7
|
+
console.log(`Usage: fuul-mcp <command>
|
|
8
|
+
|
|
9
|
+
Commands:
|
|
10
|
+
login Open browser to sign in and save tokens to ~/.fuul/tokens.json
|
|
11
|
+
logout Remove saved tokens
|
|
12
|
+
whoami Print the current user from GET /api/v1/auth/user
|
|
13
|
+
|
|
14
|
+
Environment:
|
|
15
|
+
FUUL_API_BASE_URL API origin (default https://api.fuul.xyz)
|
|
16
|
+
FUUL_OAUTH_CLIENT_ID OAuth client id (default fuul-agent)
|
|
17
|
+
FUUL_OAUTH_REDIRECT_URI Callback URL (default http://127.0.0.1:8765/callback)
|
|
18
|
+
FUUL_MCP_DEBUG Set to 1 or true for debug logs on stderr
|
|
19
|
+
`);
|
|
20
|
+
}
|
|
21
|
+
async function whoami() {
|
|
22
|
+
const env = loadEnv();
|
|
23
|
+
const store = new TokenStore();
|
|
24
|
+
const oauth = new OAuthClient(env, store);
|
|
25
|
+
const api = new FuulApiClient(env, store, oauth);
|
|
26
|
+
try {
|
|
27
|
+
const user = await api.getAuthUser();
|
|
28
|
+
console.log(JSON.stringify(user, null, 2));
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
if (e instanceof NotLoggedInError) {
|
|
32
|
+
console.error(e.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (e instanceof ApiRequestError) {
|
|
36
|
+
console.error(`Failed to load user (HTTP ${e.status}). Try: fuul-mcp login`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function main() {
|
|
43
|
+
const cmd = process.argv[2];
|
|
44
|
+
const env = loadEnv();
|
|
45
|
+
const store = new TokenStore();
|
|
46
|
+
const oauth = new OAuthClient(env, store);
|
|
47
|
+
switch (cmd) {
|
|
48
|
+
case 'login':
|
|
49
|
+
await oauth.login();
|
|
50
|
+
break;
|
|
51
|
+
case 'logout':
|
|
52
|
+
await store.clear();
|
|
53
|
+
console.log('Logged out.');
|
|
54
|
+
break;
|
|
55
|
+
case 'whoami':
|
|
56
|
+
await whoami();
|
|
57
|
+
break;
|
|
58
|
+
default:
|
|
59
|
+
printUsage();
|
|
60
|
+
process.exit(cmd ? 1 : 0);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
main().catch((err) => {
|
|
64
|
+
console.error(err instanceof Error ? err.message : err);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAE7F,SAAS,UAAU;IACjB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;CAYb,CAAC,CAAC;AACH,CAAC;AAED,KAAK,UAAU,MAAM;IACnB,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,MAAM,KAAK,GAAG,IAAI,UAAU,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,IAAI,aAAa,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IAEjD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC;QACrC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC7C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,gBAAgB,EAAE,CAAC;YAClC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACzB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,YAAY,eAAe,EAAE,CAAC;YACjC,OAAO,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC,MAAM,wBAAwB,CAAC,CAAC;YAC7E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,MAAM,KAAK,GAAG,IAAI,UAAU,EAAE,CAAC;IAC/B,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAE1C,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,OAAO;YACV,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;YACpB,MAAM;QACR,KAAK,QAAQ;YACX,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;YAEpB,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YAC3B,MAAM;QACR,KAAK,QAAQ;YACX,MAAM,MAAM,EAAE,CAAC;YACf,MAAM;QACR;YACE,UAAU,EAAE,CAAC;YACb,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const envSchema: any;
|
|
3
|
+
export type Env = z.infer<typeof envSchema> & {
|
|
4
|
+
debug: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare function loadEnv(processEnv?: NodeJS.ProcessEnv): Env;
|
|
7
|
+
/** API origin with no trailing slash (OAuth and REST live under this host). */
|
|
8
|
+
export declare function apiOriginFromEnv(env: Env): string;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,SAAS,KAMb,CAAC;AAEH,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,GAAG;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;AAMjE,wBAAgB,OAAO,CAAC,UAAU,GAAE,MAAM,CAAC,UAAwB,GAAG,GAAG,CAWxE;AAED,+EAA+E;AAC/E,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,CAEjD"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { config as loadDotEnv } from 'dotenv';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
loadDotEnv();
|
|
4
|
+
const envSchema = z.object({
|
|
5
|
+
FUUL_API_BASE_URL: z.string().url().default('https://api.fuul.xyz'),
|
|
6
|
+
FUUL_OAUTH_CLIENT_ID: z.string().min(1).default('fuul-agent'),
|
|
7
|
+
FUUL_OAUTH_REDIRECT_URI: z.string().url().default('http://127.0.0.1:8765/callback'),
|
|
8
|
+
/** Max wall time per MCP tool call (ms). Covers refresh + retry on 401. */
|
|
9
|
+
FUUL_MCP_TOOL_TIMEOUT_MS: z.coerce.number().int().positive().default(90_000),
|
|
10
|
+
});
|
|
11
|
+
function unsetIfEmpty(value) {
|
|
12
|
+
return value === undefined || value === '' ? undefined : value;
|
|
13
|
+
}
|
|
14
|
+
export function loadEnv(processEnv = process.env) {
|
|
15
|
+
const base = envSchema.parse({
|
|
16
|
+
FUUL_API_BASE_URL: unsetIfEmpty(processEnv.FUUL_API_BASE_URL),
|
|
17
|
+
FUUL_OAUTH_CLIENT_ID: unsetIfEmpty(processEnv.FUUL_OAUTH_CLIENT_ID),
|
|
18
|
+
FUUL_OAUTH_REDIRECT_URI: unsetIfEmpty(processEnv.FUUL_OAUTH_REDIRECT_URI),
|
|
19
|
+
FUUL_MCP_TOOL_TIMEOUT_MS: unsetIfEmpty(processEnv.FUUL_MCP_TOOL_TIMEOUT_MS),
|
|
20
|
+
});
|
|
21
|
+
return {
|
|
22
|
+
...base,
|
|
23
|
+
debug: processEnv.FUUL_MCP_DEBUG === '1' || processEnv.FUUL_MCP_DEBUG === 'true',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** API origin with no trailing slash (OAuth and REST live under this host). */
|
|
27
|
+
export function apiOriginFromEnv(env) {
|
|
28
|
+
return env.FUUL_API_BASE_URL.replace(/\/+$/, '');
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,UAAU,EAAE,CAAC;AAEb,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC;IACzB,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,sBAAsB,CAAC;IACnE,oBAAoB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;IAC7D,uBAAuB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,gCAAgC,CAAC;IACnF,2EAA2E;IAC3E,wBAAwB,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;CAC7E,CAAC,CAAC;AAIH,SAAS,YAAY,CAAC,KAAyB;IAC7C,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACjE,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,aAAgC,OAAO,CAAC,GAAG;IACjE,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC;QAC3B,iBAAiB,EAAE,YAAY,CAAC,UAAU,CAAC,iBAAiB,CAAC;QAC7D,oBAAoB,EAAE,YAAY,CAAC,UAAU,CAAC,oBAAoB,CAAC;QACnE,uBAAuB,EAAE,YAAY,CAAC,UAAU,CAAC,uBAAuB,CAAC;QACzE,wBAAwB,EAAE,YAAY,CAAC,UAAU,CAAC,wBAAwB,CAAC;KAC5E,CAAC,CAAC;IACH,OAAO;QACL,GAAG,IAAI;QACP,KAAK,EAAE,UAAU,CAAC,cAAc,KAAK,GAAG,IAAI,UAAU,CAAC,cAAc,KAAK,MAAM;KACjF,CAAC;AACJ,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,GAAQ;IACvC,OAAO,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { type AxiosResponse } from 'axios';
|
|
2
|
+
import { OAuthClient } from '../auth/oauth-client.js';
|
|
3
|
+
import { TokenStore } from '../auth/token-store.js';
|
|
4
|
+
import { type Env } from '../config/env.js';
|
|
5
|
+
export declare class NotLoggedInError extends Error {
|
|
6
|
+
readonly code: "NOT_LOGGED_IN";
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
export declare class ApiRequestError extends Error {
|
|
10
|
+
readonly status: number;
|
|
11
|
+
readonly body?: unknown | undefined;
|
|
12
|
+
/** Seconds until retry when HTTP 429 and Retry-After is present. */
|
|
13
|
+
readonly retryAfterSeconds?: number | undefined;
|
|
14
|
+
readonly code: "API_REQUEST_ERROR";
|
|
15
|
+
constructor(message: string, status: number, body?: unknown | undefined,
|
|
16
|
+
/** Seconds until retry when HTTP 429 and Retry-After is present. */
|
|
17
|
+
retryAfterSeconds?: number | undefined);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Authenticated Fuul API client: Bearer from ~/.fuul/tokens.json, refresh + one retry on 401.
|
|
21
|
+
*/
|
|
22
|
+
export declare class FuulApiClient {
|
|
23
|
+
private readonly tokenStore;
|
|
24
|
+
private readonly oauth;
|
|
25
|
+
private readonly http;
|
|
26
|
+
constructor(env: Env, tokenStore: TokenStore, oauth: OAuthClient);
|
|
27
|
+
/** GET /api/v1/auth/user — same contract as `fuul-mcp whoami`. */
|
|
28
|
+
getAuthUser(): Promise<unknown>;
|
|
29
|
+
/**
|
|
30
|
+
* Authenticated GET (Bearer + refresh on 401). Exposes status and headers for cacheable endpoints (e.g. metadata).
|
|
31
|
+
*/
|
|
32
|
+
getAuthorized(url: string, extraHeaders?: Record<string, string>, query?: Record<string, unknown>): Promise<{
|
|
33
|
+
status: number;
|
|
34
|
+
data: unknown;
|
|
35
|
+
etag: string | undefined;
|
|
36
|
+
cacheControl: string | undefined;
|
|
37
|
+
retryAfterSeconds: number | undefined;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* GET expecting 2xx JSON body; throws {@link ApiRequestError} otherwise (including 429 with retry hint).
|
|
41
|
+
*/
|
|
42
|
+
getJson(url: string, options?: {
|
|
43
|
+
query?: Record<string, unknown>;
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
}): Promise<unknown>;
|
|
46
|
+
postJson(url: string, body: unknown): Promise<unknown>;
|
|
47
|
+
patchJson(url: string, body: unknown): Promise<unknown>;
|
|
48
|
+
private executeAuthorizedRequest;
|
|
49
|
+
}
|
|
50
|
+
export declare function throwIfNotSuccess(res: AxiosResponse): void;
|
|
51
|
+
//# sourceMappingURL=fuul-api-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuul-api-client.d.ts","sourceRoot":"","sources":["../../src/http/fuul-api-client.ts"],"names":[],"mappings":"AAAA,OAAc,EAAsB,KAAK,aAAa,EAAE,MAAM,OAAO,CAAC;AAEtE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAoB,KAAK,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAK9D,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAG,eAAe,CAAU;;CAO1C;AAED,qBAAa,eAAgB,SAAQ,KAAK;IAKtC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO;IACvB,oEAAoE;IACpE,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM;IAPrC,QAAQ,CAAC,IAAI,EAAG,mBAAmB,CAAU;gBAG3C,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,OAAO,YAAA;IACvB,oEAAoE;IAC3D,iBAAiB,CAAC,EAAE,MAAM,YAAA;CAMtC;AAWD;;GAEG;AACH,qBAAa,aAAa;IAKtB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,KAAK;IALxB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;gBAGnC,GAAG,EAAE,GAAG,EACS,UAAU,EAAE,UAAU,EACtB,KAAK,EAAE,WAAW;IAUrC,kEAAkE;IAC5D,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;IAWrC;;OAEG;IACG,aAAa,CACjB,GAAG,EAAE,MAAM,EACX,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACrC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC;QACT,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,OAAO,CAAC;QACd,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;QACzB,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;QACjC,iBAAiB,EAAE,MAAM,GAAG,SAAS,CAAC;KACvC,CAAC;IAgBF;;OAEG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAWvH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAUtD,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;YAU/C,wBAAwB;CA2BvC;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAK1D"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { apiOriginFromEnv } from '../config/env.js';
|
|
3
|
+
import { formatRateLimitMessage, parseRetryAfterFromHeaders } from './retry-after.js';
|
|
4
|
+
const REQUEST_TIMEOUT_MS = 30_000;
|
|
5
|
+
export class NotLoggedInError extends Error {
|
|
6
|
+
code = 'NOT_LOGGED_IN';
|
|
7
|
+
constructor() {
|
|
8
|
+
super('Not logged in. Run: fuul-mcp login');
|
|
9
|
+
this.name = 'NotLoggedInError';
|
|
10
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class ApiRequestError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
body;
|
|
16
|
+
retryAfterSeconds;
|
|
17
|
+
code = 'API_REQUEST_ERROR';
|
|
18
|
+
constructor(message, status, body,
|
|
19
|
+
/** Seconds until retry when HTTP 429 and Retry-After is present. */
|
|
20
|
+
retryAfterSeconds) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.status = status;
|
|
23
|
+
this.body = body;
|
|
24
|
+
this.retryAfterSeconds = retryAfterSeconds;
|
|
25
|
+
this.name = 'ApiRequestError';
|
|
26
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Authenticated Fuul API client: Bearer from ~/.fuul/tokens.json, refresh + one retry on 401.
|
|
31
|
+
*/
|
|
32
|
+
export class FuulApiClient {
|
|
33
|
+
tokenStore;
|
|
34
|
+
oauth;
|
|
35
|
+
http;
|
|
36
|
+
constructor(env, tokenStore, oauth) {
|
|
37
|
+
this.tokenStore = tokenStore;
|
|
38
|
+
this.oauth = oauth;
|
|
39
|
+
this.http = axios.create({
|
|
40
|
+
baseURL: apiOriginFromEnv(env),
|
|
41
|
+
timeout: REQUEST_TIMEOUT_MS,
|
|
42
|
+
headers: { 'Content-Type': 'application/json' },
|
|
43
|
+
validateStatus: () => true,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** GET /api/v1/auth/user — same contract as `fuul-mcp whoami`. */
|
|
47
|
+
async getAuthUser() {
|
|
48
|
+
const res = await this.executeAuthorizedRequest({
|
|
49
|
+
method: 'GET',
|
|
50
|
+
url: '/api/v1/auth/user',
|
|
51
|
+
});
|
|
52
|
+
if (res.status !== 200) {
|
|
53
|
+
throw apiErrorFromResponse(res);
|
|
54
|
+
}
|
|
55
|
+
return res.data;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Authenticated GET (Bearer + refresh on 401). Exposes status and headers for cacheable endpoints (e.g. metadata).
|
|
59
|
+
*/
|
|
60
|
+
async getAuthorized(url, extraHeaders, query) {
|
|
61
|
+
const res = await this.executeAuthorizedRequest({
|
|
62
|
+
method: 'GET',
|
|
63
|
+
url,
|
|
64
|
+
headers: extraHeaders,
|
|
65
|
+
params: query,
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
status: res.status,
|
|
69
|
+
data: res.data,
|
|
70
|
+
etag: readHeader(res, 'etag'),
|
|
71
|
+
cacheControl: readHeader(res, 'cache-control'),
|
|
72
|
+
retryAfterSeconds: parseRetryAfterFromHeaders(res.headers),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* GET expecting 2xx JSON body; throws {@link ApiRequestError} otherwise (including 429 with retry hint).
|
|
77
|
+
*/
|
|
78
|
+
async getJson(url, options) {
|
|
79
|
+
const res = await this.executeAuthorizedRequest({
|
|
80
|
+
method: 'GET',
|
|
81
|
+
url,
|
|
82
|
+
headers: options?.headers,
|
|
83
|
+
params: options?.query,
|
|
84
|
+
});
|
|
85
|
+
throwIfNotSuccess(res);
|
|
86
|
+
return res.data;
|
|
87
|
+
}
|
|
88
|
+
async postJson(url, body) {
|
|
89
|
+
const res = await this.executeAuthorizedRequest({
|
|
90
|
+
method: 'POST',
|
|
91
|
+
url,
|
|
92
|
+
data: body,
|
|
93
|
+
});
|
|
94
|
+
throwIfNotSuccess(res);
|
|
95
|
+
return res.data;
|
|
96
|
+
}
|
|
97
|
+
async patchJson(url, body) {
|
|
98
|
+
const res = await this.executeAuthorizedRequest({
|
|
99
|
+
method: 'PATCH',
|
|
100
|
+
url,
|
|
101
|
+
data: body,
|
|
102
|
+
});
|
|
103
|
+
throwIfNotSuccess(res);
|
|
104
|
+
return res.data;
|
|
105
|
+
}
|
|
106
|
+
async executeAuthorizedRequest(opts) {
|
|
107
|
+
let tokens = await this.tokenStore.read();
|
|
108
|
+
if (!tokens?.access_token) {
|
|
109
|
+
throw new NotLoggedInError();
|
|
110
|
+
}
|
|
111
|
+
const run = (accessToken) => this.http.request({
|
|
112
|
+
method: opts.method,
|
|
113
|
+
url: opts.url,
|
|
114
|
+
headers: { Authorization: `Bearer ${accessToken}`, ...opts.headers },
|
|
115
|
+
params: opts.params,
|
|
116
|
+
data: opts.data,
|
|
117
|
+
});
|
|
118
|
+
let res = await run(tokens.access_token);
|
|
119
|
+
if (res.status === 401 && tokens.refresh_token) {
|
|
120
|
+
const refreshed = await this.oauth.refreshFromStore();
|
|
121
|
+
if (refreshed) {
|
|
122
|
+
tokens = refreshed;
|
|
123
|
+
res = await run(tokens.access_token);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return res;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function throwIfNotSuccess(res) {
|
|
130
|
+
if (res.status >= 200 && res.status < 300) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
throw apiErrorFromResponse(res);
|
|
134
|
+
}
|
|
135
|
+
function apiErrorFromResponse(res) {
|
|
136
|
+
if (res.status === 429) {
|
|
137
|
+
const retryAfter = parseRetryAfterFromHeaders(res.headers);
|
|
138
|
+
return new ApiRequestError(formatRateLimitMessage(retryAfter), 429, res.data, retryAfter);
|
|
139
|
+
}
|
|
140
|
+
return new ApiRequestError(messageFromBody(res.data, res.status), res.status, res.data);
|
|
141
|
+
}
|
|
142
|
+
function messageFromBody(data, status) {
|
|
143
|
+
if (data && typeof data === 'object') {
|
|
144
|
+
const o = data;
|
|
145
|
+
if (typeof o.message === 'string') {
|
|
146
|
+
return o.message;
|
|
147
|
+
}
|
|
148
|
+
if (Array.isArray(o.message)) {
|
|
149
|
+
return o.message.map(String).join('; ');
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return `Request failed (HTTP ${status})`;
|
|
153
|
+
}
|
|
154
|
+
function readHeader(res, name) {
|
|
155
|
+
const raw = res.headers[name] ?? res.headers[name.toLowerCase()];
|
|
156
|
+
if (raw == null) {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
return Array.isArray(raw) ? String(raw[0]) : String(raw);
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=fuul-api-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fuul-api-client.js","sourceRoot":"","sources":["../../src/http/fuul-api-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize query params for NestJS controllers that expect repeated keys
|
|
3
|
+
* (e.g. statuses=Active&statuses=Paused) instead of axios default bracket indexing.
|
|
4
|
+
*/
|
|
5
|
+
export declare function buildNestQueryString(params: Record<string, unknown>): string;
|
|
6
|
+
//# sourceMappingURL=nest-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest-query.d.ts","sourceRoot":"","sources":["../../src/http/nest-query.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAiB5E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serialize query params for NestJS controllers that expect repeated keys
|
|
3
|
+
* (e.g. statuses=Active&statuses=Paused) instead of axios default bracket indexing.
|
|
4
|
+
*/
|
|
5
|
+
export function buildNestQueryString(params) {
|
|
6
|
+
const search = new URLSearchParams();
|
|
7
|
+
for (const [key, value] of Object.entries(params)) {
|
|
8
|
+
if (value === undefined || value === null || value === '') {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
for (const item of value) {
|
|
13
|
+
if (item !== undefined && item !== null && item !== '') {
|
|
14
|
+
search.append(key, String(item));
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
search.append(key, String(value));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return search.toString();
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=nest-query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest-query.js","sourceRoot":"","sources":["../../src/http/nest-query.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAA+B;IAClE,MAAM,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;IACrC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAClD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;YAC1D,SAAS;QACX,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE,EAAE,CAAC;oBACvD,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBACnC,CAAC;YACH,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;AAC3B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AxiosResponseHeaders, RawAxiosResponseHeaders } from 'axios';
|
|
2
|
+
/**
|
|
3
|
+
* Parses Retry-After from response headers (seconds or HTTP-date).
|
|
4
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseRetryAfterFromHeaders(headers: RawAxiosResponseHeaders | AxiosResponseHeaders): number | undefined;
|
|
7
|
+
export declare function formatRateLimitMessage(retryAfterSeconds?: number): string;
|
|
8
|
+
//# sourceMappingURL=retry-after.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-after.d.ts","sourceRoot":"","sources":["../../src/http/retry-after.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,OAAO,CAAC;AAE3E;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,uBAAuB,GAAG,oBAAoB,GAAG,MAAM,GAAG,SAAS,CAgBtH;AAED,wBAAgB,sBAAsB,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,MAAM,CAKzE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses Retry-After from response headers (seconds or HTTP-date).
|
|
3
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Retry-After
|
|
4
|
+
*/
|
|
5
|
+
export function parseRetryAfterFromHeaders(headers) {
|
|
6
|
+
const raw = headers['retry-after'] ?? headers['Retry-After'];
|
|
7
|
+
const v = Array.isArray(raw) ? raw[0] : raw;
|
|
8
|
+
if (v == null || v === '') {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
const s = String(v).trim();
|
|
12
|
+
const asNum = Number(s);
|
|
13
|
+
if (Number.isFinite(asNum) && asNum >= 0) {
|
|
14
|
+
return Math.ceil(asNum);
|
|
15
|
+
}
|
|
16
|
+
const dateMs = Date.parse(s);
|
|
17
|
+
if (Number.isFinite(dateMs)) {
|
|
18
|
+
return Math.max(0, Math.ceil((dateMs - Date.now()) / 1000));
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
export function formatRateLimitMessage(retryAfterSeconds) {
|
|
23
|
+
if (retryAfterSeconds != null && retryAfterSeconds > 0) {
|
|
24
|
+
return `Rate limited (HTTP 429). Retry after approximately ${retryAfterSeconds} second(s). Back off and avoid parallel bursts.`;
|
|
25
|
+
}
|
|
26
|
+
return 'Rate limited (HTTP 429). Back off and retry later; respect Retry-After when present.';
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=retry-after.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry-after.js","sourceRoot":"","sources":["../../src/http/retry-after.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAuD;IAChG,MAAM,GAAG,GAAG,OAAO,CAAC,aAAa,CAAC,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7D,MAAM,CAAC,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC5C,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC7B,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,iBAA0B;IAC/D,IAAI,iBAAiB,IAAI,IAAI,IAAI,iBAAiB,GAAG,CAAC,EAAE,CAAC;QACvD,OAAO,sDAAsD,iBAAiB,iDAAiD,CAAC;IAClI,CAAC;IACD,OAAO,sFAAsF,CAAC;AAChG,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|