@apitap/core 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/LICENSE +60 -0
- package/README.md +362 -0
- package/SKILL.md +270 -0
- package/dist/auth/crypto.d.ts +31 -0
- package/dist/auth/crypto.js +66 -0
- package/dist/auth/crypto.js.map +1 -0
- package/dist/auth/handoff.d.ts +29 -0
- package/dist/auth/handoff.js +180 -0
- package/dist/auth/handoff.js.map +1 -0
- package/dist/auth/manager.d.ts +46 -0
- package/dist/auth/manager.js +127 -0
- package/dist/auth/manager.js.map +1 -0
- package/dist/auth/oauth-refresh.d.ts +16 -0
- package/dist/auth/oauth-refresh.js +91 -0
- package/dist/auth/oauth-refresh.js.map +1 -0
- package/dist/auth/refresh.d.ts +43 -0
- package/dist/auth/refresh.js +217 -0
- package/dist/auth/refresh.js.map +1 -0
- package/dist/capture/anti-bot.d.ts +15 -0
- package/dist/capture/anti-bot.js +43 -0
- package/dist/capture/anti-bot.js.map +1 -0
- package/dist/capture/blocklist.d.ts +6 -0
- package/dist/capture/blocklist.js +70 -0
- package/dist/capture/blocklist.js.map +1 -0
- package/dist/capture/body-diff.d.ts +8 -0
- package/dist/capture/body-diff.js +102 -0
- package/dist/capture/body-diff.js.map +1 -0
- package/dist/capture/body-variables.d.ts +13 -0
- package/dist/capture/body-variables.js +142 -0
- package/dist/capture/body-variables.js.map +1 -0
- package/dist/capture/domain.d.ts +8 -0
- package/dist/capture/domain.js +34 -0
- package/dist/capture/domain.js.map +1 -0
- package/dist/capture/entropy.d.ts +33 -0
- package/dist/capture/entropy.js +100 -0
- package/dist/capture/entropy.js.map +1 -0
- package/dist/capture/filter.d.ts +11 -0
- package/dist/capture/filter.js +49 -0
- package/dist/capture/filter.js.map +1 -0
- package/dist/capture/graphql.d.ts +21 -0
- package/dist/capture/graphql.js +99 -0
- package/dist/capture/graphql.js.map +1 -0
- package/dist/capture/idle.d.ts +23 -0
- package/dist/capture/idle.js +44 -0
- package/dist/capture/idle.js.map +1 -0
- package/dist/capture/monitor.d.ts +26 -0
- package/dist/capture/monitor.js +183 -0
- package/dist/capture/monitor.js.map +1 -0
- package/dist/capture/oauth-detector.d.ts +18 -0
- package/dist/capture/oauth-detector.js +96 -0
- package/dist/capture/oauth-detector.js.map +1 -0
- package/dist/capture/pagination.d.ts +9 -0
- package/dist/capture/pagination.js +40 -0
- package/dist/capture/pagination.js.map +1 -0
- package/dist/capture/parameterize.d.ts +17 -0
- package/dist/capture/parameterize.js +63 -0
- package/dist/capture/parameterize.js.map +1 -0
- package/dist/capture/scrubber.d.ts +5 -0
- package/dist/capture/scrubber.js +38 -0
- package/dist/capture/scrubber.js.map +1 -0
- package/dist/capture/session.d.ts +46 -0
- package/dist/capture/session.js +445 -0
- package/dist/capture/session.js.map +1 -0
- package/dist/capture/token-detector.d.ts +16 -0
- package/dist/capture/token-detector.js +62 -0
- package/dist/capture/token-detector.js.map +1 -0
- package/dist/capture/verifier.d.ts +17 -0
- package/dist/capture/verifier.js +147 -0
- package/dist/capture/verifier.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +930 -0
- package/dist/cli.js.map +1 -0
- package/dist/discovery/auth.d.ts +17 -0
- package/dist/discovery/auth.js +81 -0
- package/dist/discovery/auth.js.map +1 -0
- package/dist/discovery/fetch.d.ts +17 -0
- package/dist/discovery/fetch.js +59 -0
- package/dist/discovery/fetch.js.map +1 -0
- package/dist/discovery/frameworks.d.ts +11 -0
- package/dist/discovery/frameworks.js +249 -0
- package/dist/discovery/frameworks.js.map +1 -0
- package/dist/discovery/index.d.ts +21 -0
- package/dist/discovery/index.js +219 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/openapi.d.ts +13 -0
- package/dist/discovery/openapi.js +175 -0
- package/dist/discovery/openapi.js.map +1 -0
- package/dist/discovery/probes.d.ts +9 -0
- package/dist/discovery/probes.js +70 -0
- package/dist/discovery/probes.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/inspect/report.d.ts +52 -0
- package/dist/inspect/report.js +191 -0
- package/dist/inspect/report.js.map +1 -0
- package/dist/mcp.d.ts +8 -0
- package/dist/mcp.js +526 -0
- package/dist/mcp.js.map +1 -0
- package/dist/orchestration/browse.d.ts +38 -0
- package/dist/orchestration/browse.js +198 -0
- package/dist/orchestration/browse.js.map +1 -0
- package/dist/orchestration/cache.d.ts +15 -0
- package/dist/orchestration/cache.js +24 -0
- package/dist/orchestration/cache.js.map +1 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/plugin.js +158 -0
- package/dist/plugin.js.map +1 -0
- package/dist/read/decoders/deepwiki.d.ts +2 -0
- package/dist/read/decoders/deepwiki.js +148 -0
- package/dist/read/decoders/deepwiki.js.map +1 -0
- package/dist/read/decoders/grokipedia.d.ts +2 -0
- package/dist/read/decoders/grokipedia.js +210 -0
- package/dist/read/decoders/grokipedia.js.map +1 -0
- package/dist/read/decoders/hackernews.d.ts +2 -0
- package/dist/read/decoders/hackernews.js +168 -0
- package/dist/read/decoders/hackernews.js.map +1 -0
- package/dist/read/decoders/index.d.ts +2 -0
- package/dist/read/decoders/index.js +12 -0
- package/dist/read/decoders/index.js.map +1 -0
- package/dist/read/decoders/reddit.d.ts +2 -0
- package/dist/read/decoders/reddit.js +142 -0
- package/dist/read/decoders/reddit.js.map +1 -0
- package/dist/read/decoders/twitter.d.ts +12 -0
- package/dist/read/decoders/twitter.js +187 -0
- package/dist/read/decoders/twitter.js.map +1 -0
- package/dist/read/decoders/wikipedia.d.ts +2 -0
- package/dist/read/decoders/wikipedia.js +66 -0
- package/dist/read/decoders/wikipedia.js.map +1 -0
- package/dist/read/decoders/youtube.d.ts +2 -0
- package/dist/read/decoders/youtube.js +69 -0
- package/dist/read/decoders/youtube.js.map +1 -0
- package/dist/read/extract.d.ts +25 -0
- package/dist/read/extract.js +320 -0
- package/dist/read/extract.js.map +1 -0
- package/dist/read/index.d.ts +14 -0
- package/dist/read/index.js +66 -0
- package/dist/read/index.js.map +1 -0
- package/dist/read/peek.d.ts +9 -0
- package/dist/read/peek.js +137 -0
- package/dist/read/peek.js.map +1 -0
- package/dist/read/types.d.ts +44 -0
- package/dist/read/types.js +3 -0
- package/dist/read/types.js.map +1 -0
- package/dist/replay/engine.d.ts +53 -0
- package/dist/replay/engine.js +441 -0
- package/dist/replay/engine.js.map +1 -0
- package/dist/replay/truncate.d.ts +16 -0
- package/dist/replay/truncate.js +92 -0
- package/dist/replay/truncate.js.map +1 -0
- package/dist/serve.d.ts +31 -0
- package/dist/serve.js +149 -0
- package/dist/serve.js.map +1 -0
- package/dist/skill/generator.d.ts +44 -0
- package/dist/skill/generator.js +419 -0
- package/dist/skill/generator.js.map +1 -0
- package/dist/skill/importer.d.ts +26 -0
- package/dist/skill/importer.js +80 -0
- package/dist/skill/importer.js.map +1 -0
- package/dist/skill/search.d.ts +19 -0
- package/dist/skill/search.js +51 -0
- package/dist/skill/search.js.map +1 -0
- package/dist/skill/signing.d.ts +16 -0
- package/dist/skill/signing.js +34 -0
- package/dist/skill/signing.js.map +1 -0
- package/dist/skill/ssrf.d.ts +27 -0
- package/dist/skill/ssrf.js +210 -0
- package/dist/skill/ssrf.js.map +1 -0
- package/dist/skill/store.d.ts +7 -0
- package/dist/skill/store.js +93 -0
- package/dist/skill/store.js.map +1 -0
- package/dist/stats/report.d.ts +26 -0
- package/dist/stats/report.js +157 -0
- package/dist/stats/report.js.map +1 -0
- package/dist/types.d.ts +214 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +58 -0
- package/src/auth/crypto.ts +92 -0
- package/src/auth/handoff.ts +229 -0
- package/src/auth/manager.ts +140 -0
- package/src/auth/oauth-refresh.ts +120 -0
- package/src/auth/refresh.ts +300 -0
- package/src/capture/anti-bot.ts +63 -0
- package/src/capture/blocklist.ts +75 -0
- package/src/capture/body-diff.ts +109 -0
- package/src/capture/body-variables.ts +156 -0
- package/src/capture/domain.ts +34 -0
- package/src/capture/entropy.ts +121 -0
- package/src/capture/filter.ts +56 -0
- package/src/capture/graphql.ts +124 -0
- package/src/capture/idle.ts +45 -0
- package/src/capture/monitor.ts +224 -0
- package/src/capture/oauth-detector.ts +106 -0
- package/src/capture/pagination.ts +49 -0
- package/src/capture/parameterize.ts +68 -0
- package/src/capture/scrubber.ts +49 -0
- package/src/capture/session.ts +502 -0
- package/src/capture/token-detector.ts +76 -0
- package/src/capture/verifier.ts +171 -0
- package/src/cli.ts +1031 -0
- package/src/discovery/auth.ts +99 -0
- package/src/discovery/fetch.ts +85 -0
- package/src/discovery/frameworks.ts +231 -0
- package/src/discovery/index.ts +256 -0
- package/src/discovery/openapi.ts +230 -0
- package/src/discovery/probes.ts +76 -0
- package/src/index.ts +26 -0
- package/src/inspect/report.ts +247 -0
- package/src/mcp.ts +618 -0
- package/src/orchestration/browse.ts +250 -0
- package/src/orchestration/cache.ts +37 -0
- package/src/plugin.ts +188 -0
- package/src/read/decoders/deepwiki.ts +180 -0
- package/src/read/decoders/grokipedia.ts +246 -0
- package/src/read/decoders/hackernews.ts +198 -0
- package/src/read/decoders/index.ts +15 -0
- package/src/read/decoders/reddit.ts +158 -0
- package/src/read/decoders/twitter.ts +211 -0
- package/src/read/decoders/wikipedia.ts +75 -0
- package/src/read/decoders/youtube.ts +75 -0
- package/src/read/extract.ts +396 -0
- package/src/read/index.ts +78 -0
- package/src/read/peek.ts +175 -0
- package/src/read/types.ts +37 -0
- package/src/replay/engine.ts +559 -0
- package/src/replay/truncate.ts +116 -0
- package/src/serve.ts +189 -0
- package/src/skill/generator.ts +473 -0
- package/src/skill/importer.ts +107 -0
- package/src/skill/search.ts +76 -0
- package/src/skill/signing.ts +36 -0
- package/src/skill/ssrf.ts +238 -0
- package/src/skill/store.ts +107 -0
- package/src/stats/report.ts +208 -0
- package/src/types.ts +233 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { SkillFile } from '../types.js';
|
|
2
|
+
import type { AuthManager } from '../auth/manager.js';
|
|
3
|
+
export interface ReplayOptions {
|
|
4
|
+
/** User-provided parameters for path, query, and body substitution */
|
|
5
|
+
params?: Record<string, string>;
|
|
6
|
+
/** Auth manager for token injection (optional) */
|
|
7
|
+
authManager?: AuthManager;
|
|
8
|
+
/** Domain for auth lookups (required if authManager provided) */
|
|
9
|
+
domain?: string;
|
|
10
|
+
/** Force token refresh before replay (requires authManager) */
|
|
11
|
+
fresh?: boolean;
|
|
12
|
+
/** Maximum response size in bytes. If set, truncates large responses. */
|
|
13
|
+
maxBytes?: number;
|
|
14
|
+
/** @internal Skip SSRF check — for testing only */
|
|
15
|
+
_skipSsrfCheck?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface ReplayResult {
|
|
18
|
+
status: number;
|
|
19
|
+
headers: Record<string, string>;
|
|
20
|
+
data: unknown;
|
|
21
|
+
/** Whether tokens were refreshed during this replay */
|
|
22
|
+
refreshed?: boolean;
|
|
23
|
+
/** Whether the response was truncated to fit maxBytes */
|
|
24
|
+
truncated?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Replay a captured API endpoint.
|
|
28
|
+
*
|
|
29
|
+
* @param skill - Skill file containing endpoint definitions
|
|
30
|
+
* @param endpointId - ID of the endpoint to replay
|
|
31
|
+
* @param optionsOrParams - Either ReplayOptions object or params directly (for backward compat)
|
|
32
|
+
*/
|
|
33
|
+
export declare function replayEndpoint(skill: SkillFile, endpointId: string, optionsOrParams?: ReplayOptions | Record<string, string>): Promise<ReplayResult>;
|
|
34
|
+
export interface BatchReplayRequest {
|
|
35
|
+
domain: string;
|
|
36
|
+
endpointId: string;
|
|
37
|
+
params?: Record<string, string>;
|
|
38
|
+
}
|
|
39
|
+
export interface BatchReplayResult {
|
|
40
|
+
domain: string;
|
|
41
|
+
endpointId: string;
|
|
42
|
+
status: number;
|
|
43
|
+
data: unknown;
|
|
44
|
+
error?: string;
|
|
45
|
+
tier?: string;
|
|
46
|
+
capturedAt?: string;
|
|
47
|
+
truncated?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare function replayMultiple(requests: BatchReplayRequest[], options?: {
|
|
50
|
+
skillsDir?: string;
|
|
51
|
+
maxBytes?: number;
|
|
52
|
+
_skipSsrfCheck?: boolean;
|
|
53
|
+
}): Promise<BatchReplayResult[]>;
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import { substituteBodyVariables } from '../capture/body-variables.js';
|
|
2
|
+
import { parseJwtClaims } from '../capture/entropy.js';
|
|
3
|
+
import { refreshTokens } from '../auth/refresh.js';
|
|
4
|
+
import { truncateResponse } from './truncate.js';
|
|
5
|
+
import { resolveAndValidateUrl } from '../skill/ssrf.js';
|
|
6
|
+
// Header security: prevent header injection from skill files
|
|
7
|
+
const ALLOWED_SKILL_HEADERS = new Set([
|
|
8
|
+
'accept', 'accept-language', 'accept-encoding',
|
|
9
|
+
'content-type', 'content-length',
|
|
10
|
+
'x-requested-with', 'x-api-key',
|
|
11
|
+
'origin', 'referer',
|
|
12
|
+
'user-agent',
|
|
13
|
+
// Auth headers are injected separately from encrypted storage, not from skill file
|
|
14
|
+
]);
|
|
15
|
+
const BLOCKED_HEADERS = new Set([
|
|
16
|
+
'host', 'x-forwarded-for', 'x-forwarded-host', 'x-forwarded-proto',
|
|
17
|
+
'x-real-ip', 'forwarded', 'via',
|
|
18
|
+
'cookie', 'set-cookie',
|
|
19
|
+
'authorization', // Must come from auth manager, not skill file
|
|
20
|
+
'proxy-authorization',
|
|
21
|
+
'transfer-encoding', 'te', 'trailer',
|
|
22
|
+
'connection', 'upgrade',
|
|
23
|
+
]);
|
|
24
|
+
/**
|
|
25
|
+
* Extract default path param values from an example URL by comparing
|
|
26
|
+
* it to the parameterized path template.
|
|
27
|
+
*/
|
|
28
|
+
function extractPathDefaults(pathTemplate, exampleUrl) {
|
|
29
|
+
const defaults = {};
|
|
30
|
+
try {
|
|
31
|
+
const examplePath = new URL(exampleUrl).pathname;
|
|
32
|
+
const templateParts = pathTemplate.split('/');
|
|
33
|
+
const exampleParts = examplePath.split('/');
|
|
34
|
+
for (let i = 0; i < templateParts.length && i < exampleParts.length; i++) {
|
|
35
|
+
if (templateParts[i].startsWith(':')) {
|
|
36
|
+
const paramName = templateParts[i].slice(1);
|
|
37
|
+
defaults[paramName] = exampleParts[i];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Invalid example URL — no defaults
|
|
43
|
+
}
|
|
44
|
+
return defaults;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Substitute :param placeholders in a path with values.
|
|
48
|
+
*/
|
|
49
|
+
function substitutePath(pathTemplate, params) {
|
|
50
|
+
return pathTemplate.replace(/:([a-zA-Z_]+)/g, (match, name) => {
|
|
51
|
+
return params[name] ?? match;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Detect if options object is new-style ReplayOptions or legacy params.
|
|
56
|
+
* ReplayOptions has keys like authManager, domain, fresh, or params.
|
|
57
|
+
* Legacy params only have string values.
|
|
58
|
+
*/
|
|
59
|
+
function normalizeOptions(optionsOrParams) {
|
|
60
|
+
if (!optionsOrParams) {
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
// Check for ReplayOptions signature (has known option keys or non-string values)
|
|
64
|
+
const hasOptionKeys = 'authManager' in optionsOrParams ||
|
|
65
|
+
'domain' in optionsOrParams ||
|
|
66
|
+
'fresh' in optionsOrParams ||
|
|
67
|
+
'params' in optionsOrParams ||
|
|
68
|
+
'maxBytes' in optionsOrParams ||
|
|
69
|
+
'_skipSsrfCheck' in optionsOrParams;
|
|
70
|
+
if (hasOptionKeys) {
|
|
71
|
+
return optionsOrParams;
|
|
72
|
+
}
|
|
73
|
+
// Legacy: treat entire object as params
|
|
74
|
+
return { params: optionsOrParams };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Wrap a 401/403 response with structured auth guidance.
|
|
78
|
+
*/
|
|
79
|
+
function wrapAuthError(status, originalData, domain) {
|
|
80
|
+
if (status !== 401 && status !== 403)
|
|
81
|
+
return originalData;
|
|
82
|
+
return {
|
|
83
|
+
status,
|
|
84
|
+
error: 'Authentication required',
|
|
85
|
+
suggestion: `Use apitap_auth_request to log in to ${domain}`,
|
|
86
|
+
domain,
|
|
87
|
+
originalResponse: originalData,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Replay a captured API endpoint.
|
|
92
|
+
*
|
|
93
|
+
* @param skill - Skill file containing endpoint definitions
|
|
94
|
+
* @param endpointId - ID of the endpoint to replay
|
|
95
|
+
* @param optionsOrParams - Either ReplayOptions object or params directly (for backward compat)
|
|
96
|
+
*/
|
|
97
|
+
export async function replayEndpoint(skill, endpointId, optionsOrParams) {
|
|
98
|
+
// Normalize options: support both new ReplayOptions and legacy params-only
|
|
99
|
+
const options = normalizeOptions(optionsOrParams);
|
|
100
|
+
const { params = {}, authManager, domain } = options;
|
|
101
|
+
const endpoint = skill.endpoints.find(e => e.id === endpointId);
|
|
102
|
+
if (!endpoint) {
|
|
103
|
+
throw new Error(`Endpoint "${endpointId}" not found in skill for ${skill.domain}. ` +
|
|
104
|
+
`Available: ${skill.endpoints.map(e => e.id).join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
// Resolve path: substitute :param placeholders
|
|
107
|
+
let resolvedPath = endpoint.path;
|
|
108
|
+
if (resolvedPath.includes(':')) {
|
|
109
|
+
const defaults = extractPathDefaults(endpoint.path, endpoint.examples.request.url);
|
|
110
|
+
const merged = { ...defaults, ...params };
|
|
111
|
+
resolvedPath = substitutePath(resolvedPath, merged);
|
|
112
|
+
}
|
|
113
|
+
const url = new URL(resolvedPath, skill.baseUrl);
|
|
114
|
+
// Apply query params: start with captured defaults, override with provided params
|
|
115
|
+
for (const [key, val] of Object.entries(endpoint.queryParams)) {
|
|
116
|
+
url.searchParams.set(key, val.example);
|
|
117
|
+
}
|
|
118
|
+
if (params) {
|
|
119
|
+
for (const [key, val] of Object.entries(params)) {
|
|
120
|
+
// Skip path params (already handled above)
|
|
121
|
+
if (endpoint.path.includes(`:${key}`))
|
|
122
|
+
continue;
|
|
123
|
+
// Skip body variables (they have dots in the path)
|
|
124
|
+
if (key.includes('.'))
|
|
125
|
+
continue;
|
|
126
|
+
url.searchParams.set(key, val);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// SSRF validation — block requests to private/internal IPs and get resolved URL
|
|
130
|
+
let fetchUrl = url.toString();
|
|
131
|
+
if (!options._skipSsrfCheck) {
|
|
132
|
+
const ssrfCheck = await resolveAndValidateUrl(url.toString());
|
|
133
|
+
if (!ssrfCheck.safe) {
|
|
134
|
+
throw new Error(`SSRF blocked: ${ssrfCheck.reason}`);
|
|
135
|
+
}
|
|
136
|
+
// Use resolved IP to prevent DNS rebinding
|
|
137
|
+
if (ssrfCheck.resolvedUrl) {
|
|
138
|
+
fetchUrl = ssrfCheck.resolvedUrl;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Prepare request body if present
|
|
142
|
+
let body;
|
|
143
|
+
const headers = { ...endpoint.headers };
|
|
144
|
+
// Filter headers from skill file — block dangerous headers
|
|
145
|
+
for (const key of Object.keys(headers)) {
|
|
146
|
+
const lower = key.toLowerCase();
|
|
147
|
+
if (BLOCKED_HEADERS.has(lower) || (!ALLOWED_SKILL_HEADERS.has(lower) && !lower.startsWith('x-'))) {
|
|
148
|
+
delete headers[key];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// If using DNS-pinned URL, preserve original Host header
|
|
152
|
+
if (fetchUrl !== url.toString()) {
|
|
153
|
+
headers['host'] = url.hostname;
|
|
154
|
+
}
|
|
155
|
+
// Inject auth header from auth manager (if available)
|
|
156
|
+
if (authManager && domain) {
|
|
157
|
+
const auth = await authManager.retrieve(domain);
|
|
158
|
+
if (auth && auth.header && auth.value) {
|
|
159
|
+
headers[auth.header] = auth.value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (endpoint.requestBody) {
|
|
163
|
+
let processedBody = endpoint.requestBody.template;
|
|
164
|
+
// Inject refreshable tokens from storage (v0.8)
|
|
165
|
+
if (authManager && domain && endpoint.requestBody.refreshableTokens?.length) {
|
|
166
|
+
const storedTokens = await authManager.retrieveTokens(domain);
|
|
167
|
+
if (storedTokens) {
|
|
168
|
+
const tokenValues = {};
|
|
169
|
+
for (const tokenName of endpoint.requestBody.refreshableTokens) {
|
|
170
|
+
if (storedTokens[tokenName]) {
|
|
171
|
+
tokenValues[tokenName] = storedTokens[tokenName].value;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (Object.keys(tokenValues).length > 0) {
|
|
175
|
+
processedBody = substituteBodyVariables(processedBody, tokenValues);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Substitute user-provided variables
|
|
180
|
+
if (params && endpoint.requestBody.variables) {
|
|
181
|
+
processedBody = substituteBodyVariables(processedBody, params);
|
|
182
|
+
}
|
|
183
|
+
// Serialize to string
|
|
184
|
+
if (typeof processedBody === 'object') {
|
|
185
|
+
body = JSON.stringify(processedBody);
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
body = processedBody;
|
|
189
|
+
}
|
|
190
|
+
// Ensure content-type is set
|
|
191
|
+
if (!headers['content-type']) {
|
|
192
|
+
headers['content-type'] = endpoint.requestBody.contentType;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Proactive JWT expiry check: skip doomed request if token is expired
|
|
196
|
+
const fresh = options.fresh ?? false;
|
|
197
|
+
let refreshed = false;
|
|
198
|
+
if (authManager && domain) {
|
|
199
|
+
if (fresh) {
|
|
200
|
+
// --fresh flag: force refresh before replay
|
|
201
|
+
const refreshResult = await refreshTokens(skill, authManager, { domain, _skipSsrfCheck: options._skipSsrfCheck });
|
|
202
|
+
if (refreshResult.success) {
|
|
203
|
+
refreshed = true;
|
|
204
|
+
// Re-inject fresh auth header
|
|
205
|
+
const freshAuth = await authManager.retrieve(domain);
|
|
206
|
+
if (freshAuth) {
|
|
207
|
+
headers[freshAuth.header] = freshAuth.value;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// Proactive: check if JWT is expired (30s buffer for clock skew)
|
|
213
|
+
const currentAuth = await authManager.retrieve(domain);
|
|
214
|
+
if (currentAuth?.value) {
|
|
215
|
+
const raw = currentAuth.value.startsWith('Bearer ')
|
|
216
|
+
? currentAuth.value.slice(7)
|
|
217
|
+
: currentAuth.value;
|
|
218
|
+
const jwt = parseJwtClaims(raw);
|
|
219
|
+
if (jwt?.exp && jwt.exp < Math.floor(Date.now() / 1000) + 30) {
|
|
220
|
+
const refreshResult = await refreshTokens(skill, authManager, { domain, _skipSsrfCheck: options._skipSsrfCheck });
|
|
221
|
+
if (refreshResult.success) {
|
|
222
|
+
refreshed = true;
|
|
223
|
+
const freshAuth = await authManager.retrieve(domain);
|
|
224
|
+
if (freshAuth) {
|
|
225
|
+
headers[freshAuth.header] = freshAuth.value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
let response = await fetch(fetchUrl, {
|
|
233
|
+
method: endpoint.method,
|
|
234
|
+
headers,
|
|
235
|
+
body,
|
|
236
|
+
signal: AbortSignal.timeout(30_000),
|
|
237
|
+
redirect: 'manual', // Don't auto-follow redirects
|
|
238
|
+
});
|
|
239
|
+
// Handle redirects with SSRF validation (single hop only)
|
|
240
|
+
if (response.status >= 300 && response.status < 400) {
|
|
241
|
+
const location = response.headers.get('location');
|
|
242
|
+
if (location) {
|
|
243
|
+
const redirectUrl = new URL(location, url);
|
|
244
|
+
let redirectFetchUrl = redirectUrl.toString();
|
|
245
|
+
if (!options._skipSsrfCheck) {
|
|
246
|
+
const redirectCheck = await resolveAndValidateUrl(redirectUrl.toString());
|
|
247
|
+
if (!redirectCheck.safe) {
|
|
248
|
+
throw new Error(`Redirect blocked (SSRF): ${redirectCheck.reason}`);
|
|
249
|
+
}
|
|
250
|
+
if (redirectCheck.resolvedUrl) {
|
|
251
|
+
redirectFetchUrl = redirectCheck.resolvedUrl;
|
|
252
|
+
headers['host'] = redirectUrl.hostname;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Follow the redirect manually (single hop to prevent chains)
|
|
256
|
+
response = await fetch(redirectFetchUrl, {
|
|
257
|
+
method: 'GET', // Redirects typically become GET
|
|
258
|
+
headers, // Forward headers (already filtered)
|
|
259
|
+
signal: AbortSignal.timeout(30_000),
|
|
260
|
+
redirect: 'manual', // Prevent chaining
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// Reactive: retry on 401/403 if we haven't already refreshed
|
|
265
|
+
if ((response.status === 401 || response.status === 403) &&
|
|
266
|
+
!refreshed &&
|
|
267
|
+
authManager &&
|
|
268
|
+
domain) {
|
|
269
|
+
const refreshResult = await refreshTokens(skill, authManager, { domain, _skipSsrfCheck: options._skipSsrfCheck });
|
|
270
|
+
if (refreshResult.success) {
|
|
271
|
+
refreshed = true;
|
|
272
|
+
// Re-inject fresh auth
|
|
273
|
+
const freshAuth = await authManager.retrieve(domain);
|
|
274
|
+
if (freshAuth) {
|
|
275
|
+
headers[freshAuth.header] = freshAuth.value;
|
|
276
|
+
}
|
|
277
|
+
// Retry the request
|
|
278
|
+
let retryResponse = await fetch(fetchUrl, {
|
|
279
|
+
method: endpoint.method,
|
|
280
|
+
headers,
|
|
281
|
+
body,
|
|
282
|
+
signal: AbortSignal.timeout(30_000),
|
|
283
|
+
redirect: 'manual',
|
|
284
|
+
});
|
|
285
|
+
// Handle redirects on retry (single hop)
|
|
286
|
+
if (retryResponse.status >= 300 && retryResponse.status < 400) {
|
|
287
|
+
const location = retryResponse.headers.get('location');
|
|
288
|
+
if (location) {
|
|
289
|
+
const redirectUrl = new URL(location, url);
|
|
290
|
+
let retryRedirectFetchUrl = redirectUrl.toString();
|
|
291
|
+
if (!options._skipSsrfCheck) {
|
|
292
|
+
const redirectCheck = await resolveAndValidateUrl(redirectUrl.toString());
|
|
293
|
+
if (!redirectCheck.safe) {
|
|
294
|
+
throw new Error(`Redirect blocked (SSRF): ${redirectCheck.reason}`);
|
|
295
|
+
}
|
|
296
|
+
if (redirectCheck.resolvedUrl) {
|
|
297
|
+
retryRedirectFetchUrl = redirectCheck.resolvedUrl;
|
|
298
|
+
headers['host'] = redirectUrl.hostname;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
retryResponse = await fetch(retryRedirectFetchUrl, {
|
|
302
|
+
method: 'GET',
|
|
303
|
+
headers,
|
|
304
|
+
signal: AbortSignal.timeout(30_000),
|
|
305
|
+
redirect: 'manual',
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
const retryHeaders = {};
|
|
310
|
+
retryResponse.headers.forEach((value, key) => {
|
|
311
|
+
retryHeaders[key] = value;
|
|
312
|
+
});
|
|
313
|
+
let retryData;
|
|
314
|
+
const retryCt = retryResponse.headers.get('content-type') ?? '';
|
|
315
|
+
const retryText = await retryResponse.text();
|
|
316
|
+
if (retryCt.includes('json') && retryText.length > 0) {
|
|
317
|
+
retryData = JSON.parse(retryText);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
retryData = retryText;
|
|
321
|
+
}
|
|
322
|
+
const retryFinalData = (retryResponse.status === 401 || retryResponse.status === 403)
|
|
323
|
+
? wrapAuthError(retryResponse.status, retryData, skill.domain)
|
|
324
|
+
: retryData;
|
|
325
|
+
if (options.maxBytes) {
|
|
326
|
+
const truncated = truncateResponse(retryFinalData, { maxBytes: options.maxBytes });
|
|
327
|
+
return {
|
|
328
|
+
status: retryResponse.status,
|
|
329
|
+
headers: retryHeaders,
|
|
330
|
+
data: truncated.data,
|
|
331
|
+
refreshed,
|
|
332
|
+
...(truncated.truncated ? { truncated: true } : {}),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
status: retryResponse.status,
|
|
337
|
+
headers: retryHeaders,
|
|
338
|
+
data: retryFinalData,
|
|
339
|
+
refreshed,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const responseHeaders = {};
|
|
344
|
+
response.headers.forEach((value, key) => {
|
|
345
|
+
responseHeaders[key] = value;
|
|
346
|
+
});
|
|
347
|
+
let data;
|
|
348
|
+
const ct = response.headers.get('content-type') ?? '';
|
|
349
|
+
const text = await response.text();
|
|
350
|
+
if (ct.includes('json') && text.length > 0) {
|
|
351
|
+
data = JSON.parse(text);
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
data = text;
|
|
355
|
+
}
|
|
356
|
+
const finalData = (response.status === 401 || response.status === 403)
|
|
357
|
+
? wrapAuthError(response.status, data, skill.domain)
|
|
358
|
+
: data;
|
|
359
|
+
// Apply truncation if maxBytes is set
|
|
360
|
+
if (options.maxBytes) {
|
|
361
|
+
const truncated = truncateResponse(finalData, { maxBytes: options.maxBytes });
|
|
362
|
+
return {
|
|
363
|
+
status: response.status,
|
|
364
|
+
headers: responseHeaders,
|
|
365
|
+
data: truncated.data,
|
|
366
|
+
...(refreshed ? { refreshed } : {}),
|
|
367
|
+
...(truncated.truncated ? { truncated: true } : {}),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
return { status: response.status, headers: responseHeaders, data: finalData, ...(refreshed ? { refreshed } : {}) };
|
|
371
|
+
}
|
|
372
|
+
export async function replayMultiple(requests, options = {}) {
|
|
373
|
+
if (requests.length === 0)
|
|
374
|
+
return [];
|
|
375
|
+
const { readSkillFile } = await import('../skill/store.js');
|
|
376
|
+
const { AuthManager, getMachineId } = await import('../auth/manager.js');
|
|
377
|
+
// Deduplicate skill file reads
|
|
378
|
+
const skillCache = new Map();
|
|
379
|
+
const uniqueDomains = [...new Set(requests.map(r => r.domain))];
|
|
380
|
+
await Promise.all(uniqueDomains.map(async (domain) => {
|
|
381
|
+
const skill = await readSkillFile(domain, options.skillsDir);
|
|
382
|
+
skillCache.set(domain, skill);
|
|
383
|
+
}));
|
|
384
|
+
// Shared auth manager
|
|
385
|
+
const machineId = await getMachineId();
|
|
386
|
+
const authManager = new AuthManager((await import('node:os')).homedir() + '/.apitap', machineId);
|
|
387
|
+
// Replay all in parallel
|
|
388
|
+
const settled = await Promise.allSettled(requests.map(async (req) => {
|
|
389
|
+
const skill = skillCache.get(req.domain);
|
|
390
|
+
if (!skill) {
|
|
391
|
+
return {
|
|
392
|
+
domain: req.domain,
|
|
393
|
+
endpointId: req.endpointId,
|
|
394
|
+
status: 0,
|
|
395
|
+
data: null,
|
|
396
|
+
error: `No skill file found for "${req.domain}"`,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const endpoint = skill.endpoints.find(e => e.id === req.endpointId);
|
|
400
|
+
const tier = endpoint?.replayability?.tier ?? 'unknown';
|
|
401
|
+
try {
|
|
402
|
+
const result = await replayEndpoint(skill, req.endpointId, {
|
|
403
|
+
params: req.params,
|
|
404
|
+
authManager,
|
|
405
|
+
domain: req.domain,
|
|
406
|
+
maxBytes: options.maxBytes,
|
|
407
|
+
_skipSsrfCheck: options._skipSsrfCheck,
|
|
408
|
+
});
|
|
409
|
+
return {
|
|
410
|
+
domain: req.domain,
|
|
411
|
+
endpointId: req.endpointId,
|
|
412
|
+
status: result.status,
|
|
413
|
+
data: result.data,
|
|
414
|
+
tier,
|
|
415
|
+
capturedAt: skill.capturedAt,
|
|
416
|
+
...(result.truncated ? { truncated: true } : {}),
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
return {
|
|
421
|
+
domain: req.domain,
|
|
422
|
+
endpointId: req.endpointId,
|
|
423
|
+
status: 0,
|
|
424
|
+
data: null,
|
|
425
|
+
error: err.message,
|
|
426
|
+
tier,
|
|
427
|
+
capturedAt: skill.capturedAt,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}));
|
|
431
|
+
return settled.map((s) => s.status === 'fulfilled'
|
|
432
|
+
? s.value
|
|
433
|
+
: {
|
|
434
|
+
domain: '',
|
|
435
|
+
endpointId: '',
|
|
436
|
+
status: 0,
|
|
437
|
+
data: null,
|
|
438
|
+
error: s.reason?.message ?? 'Unknown error',
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/replay/engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface TruncateOptions {
|
|
2
|
+
maxBytes?: number;
|
|
3
|
+
}
|
|
4
|
+
export interface TruncateResult {
|
|
5
|
+
data: unknown;
|
|
6
|
+
truncated: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Truncate a response to fit within maxBytes when serialized as JSON.
|
|
10
|
+
*
|
|
11
|
+
* - Arrays: remove items from the end until it fits. If a single item
|
|
12
|
+
* exceeds the limit, truncate long string fields within that item.
|
|
13
|
+
* - Objects: truncate long string fields largest-first.
|
|
14
|
+
* - Primitives/strings: returned as-is (or sliced if string).
|
|
15
|
+
*/
|
|
16
|
+
export declare function truncateResponse(data: unknown, options?: TruncateOptions): TruncateResult;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// src/replay/truncate.ts
|
|
2
|
+
const DEFAULT_MAX_BYTES = 50_000;
|
|
3
|
+
const STRING_CAP = 500;
|
|
4
|
+
function byteLength(s) {
|
|
5
|
+
return Buffer.byteLength(s, 'utf-8');
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Truncate long string fields in an object, largest-first, until
|
|
9
|
+
* the serialized size is under maxBytes.
|
|
10
|
+
*/
|
|
11
|
+
function truncateObjectStrings(obj, maxBytes) {
|
|
12
|
+
const result = { ...obj };
|
|
13
|
+
// Collect string fields with their lengths
|
|
14
|
+
const stringFields = [];
|
|
15
|
+
for (const [key, val] of Object.entries(result)) {
|
|
16
|
+
if (typeof val === 'string' && val.length > STRING_CAP) {
|
|
17
|
+
stringFields.push({ key, len: val.length });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Sort largest first
|
|
21
|
+
stringFields.sort((a, b) => b.len - a.len);
|
|
22
|
+
for (const { key } of stringFields) {
|
|
23
|
+
const val = result[key];
|
|
24
|
+
result[key] = val.slice(0, STRING_CAP) + '... [truncated]';
|
|
25
|
+
if (byteLength(JSON.stringify(result)) <= maxBytes)
|
|
26
|
+
break;
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Truncate a response to fit within maxBytes when serialized as JSON.
|
|
32
|
+
*
|
|
33
|
+
* - Arrays: remove items from the end until it fits. If a single item
|
|
34
|
+
* exceeds the limit, truncate long string fields within that item.
|
|
35
|
+
* - Objects: truncate long string fields largest-first.
|
|
36
|
+
* - Primitives/strings: returned as-is (or sliced if string).
|
|
37
|
+
*/
|
|
38
|
+
export function truncateResponse(data, options) {
|
|
39
|
+
const maxBytes = options?.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
40
|
+
if (data === null || data === undefined) {
|
|
41
|
+
return { data, truncated: false };
|
|
42
|
+
}
|
|
43
|
+
const serialized = JSON.stringify(data);
|
|
44
|
+
if (byteLength(serialized) <= maxBytes) {
|
|
45
|
+
return { data, truncated: false };
|
|
46
|
+
}
|
|
47
|
+
// Array truncation
|
|
48
|
+
if (Array.isArray(data)) {
|
|
49
|
+
const arr = [...data];
|
|
50
|
+
// Remove items from the end until it fits
|
|
51
|
+
while (arr.length > 1 && byteLength(JSON.stringify(arr)) > maxBytes) {
|
|
52
|
+
arr.pop();
|
|
53
|
+
}
|
|
54
|
+
// If single item still exceeds limit, truncate strings within it
|
|
55
|
+
if (arr.length === 1 && byteLength(JSON.stringify(arr)) > maxBytes) {
|
|
56
|
+
const item = arr[0];
|
|
57
|
+
if (item && typeof item === 'object' && !Array.isArray(item)) {
|
|
58
|
+
arr[0] = truncateObjectStrings(item, maxBytes);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// If still over (e.g. array of primitives), return empty array
|
|
62
|
+
if (arr.length === 1 && byteLength(JSON.stringify(arr)) > maxBytes) {
|
|
63
|
+
return { data: [], truncated: true };
|
|
64
|
+
}
|
|
65
|
+
return { data: arr, truncated: true };
|
|
66
|
+
}
|
|
67
|
+
// Object truncation
|
|
68
|
+
if (typeof data === 'object') {
|
|
69
|
+
const result = truncateObjectStrings(data, maxBytes);
|
|
70
|
+
return { data: result, truncated: true };
|
|
71
|
+
}
|
|
72
|
+
// String truncation as last resort
|
|
73
|
+
if (typeof data === 'string') {
|
|
74
|
+
// Binary search for the right length
|
|
75
|
+
let lo = 0;
|
|
76
|
+
let hi = data.length;
|
|
77
|
+
const suffix = '... [truncated]';
|
|
78
|
+
while (lo < hi) {
|
|
79
|
+
const mid = (lo + hi + 1) >> 1;
|
|
80
|
+
if (byteLength(JSON.stringify(data.slice(0, mid) + suffix)) <= maxBytes) {
|
|
81
|
+
lo = mid;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
hi = mid - 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return { data: data.slice(0, lo) + suffix, truncated: true };
|
|
88
|
+
}
|
|
89
|
+
// Numbers, booleans — can't truncate further
|
|
90
|
+
return { data, truncated: false };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=truncate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"truncate.js","sourceRoot":"","sources":["../../src/replay/truncate.ts"],"names":[],"mappings":"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"}
|
package/dist/serve.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { SkillFile } from './types.js';
|
|
3
|
+
export interface ServeTool {
|
|
4
|
+
name: string;
|
|
5
|
+
description: string;
|
|
6
|
+
endpointId: string;
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object';
|
|
9
|
+
properties: Record<string, {
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
}>;
|
|
13
|
+
required: string[];
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build MCP tool definitions from a skill file's endpoints.
|
|
18
|
+
* Each endpoint becomes one tool named `domain_endpointId`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildServeTools(skill: SkillFile): ServeTool[];
|
|
21
|
+
export interface ServeOptions {
|
|
22
|
+
skillsDir?: string;
|
|
23
|
+
noAuth?: boolean;
|
|
24
|
+
/** @internal Skip SSRF validation — for testing only */
|
|
25
|
+
_skipSsrfCheck?: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create an MCP server that exposes a skill file's endpoints as tools.
|
|
29
|
+
* Each endpoint becomes a callable tool that delegates to the replay engine.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createServeServer(domain: string, options?: ServeOptions): Promise<McpServer>;
|