@rog0x/mcp-api-tools 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/README.md +81 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +269 -0
- package/dist/tools/api-health.d.ts +31 -0
- package/dist/tools/api-health.js +167 -0
- package/dist/tools/header-analyzer.d.ts +56 -0
- package/dist/tools/header-analyzer.js +189 -0
- package/dist/tools/http-request.d.ts +29 -0
- package/dist/tools/http-request.js +64 -0
- package/dist/tools/jwt-decode.d.ts +12 -0
- package/dist/tools/jwt-decode.js +71 -0
- package/dist/tools/url-parser.d.ts +27 -0
- package/dist/tools/url-parser.js +73 -0
- package/package.json +26 -0
- package/src/index.ts +308 -0
- package/src/tools/api-health.ts +179 -0
- package/src/tools/header-analyzer.ts +262 -0
- package/src/tools/http-request.ts +112 -0
- package/src/tools/jwt-decode.ts +82 -0
- package/src/tools/url-parser.ts +108 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeHeaders = analyzeHeaders;
|
|
4
|
+
function get(headers, key) {
|
|
5
|
+
const lower = key.toLowerCase();
|
|
6
|
+
for (const [k, v] of Object.entries(headers)) {
|
|
7
|
+
if (k.toLowerCase() === lower)
|
|
8
|
+
return v;
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function analyzeSecurityHeaders(headers) {
|
|
13
|
+
const checks = [];
|
|
14
|
+
let score = 0;
|
|
15
|
+
const maxScore = 7;
|
|
16
|
+
const securityHeaders = [
|
|
17
|
+
{
|
|
18
|
+
header: "strict-transport-security",
|
|
19
|
+
name: "Strict-Transport-Security (HSTS)",
|
|
20
|
+
recommendation: "Add HSTS header with max-age of at least 31536000",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
header: "content-security-policy",
|
|
24
|
+
name: "Content-Security-Policy (CSP)",
|
|
25
|
+
recommendation: "Define a Content-Security-Policy to prevent XSS attacks",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
header: "x-frame-options",
|
|
29
|
+
name: "X-Frame-Options",
|
|
30
|
+
recommendation: "Set to DENY or SAMEORIGIN to prevent clickjacking",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
header: "x-content-type-options",
|
|
34
|
+
name: "X-Content-Type-Options",
|
|
35
|
+
recommendation: "Set to nosniff to prevent MIME type sniffing",
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
header: "referrer-policy",
|
|
39
|
+
name: "Referrer-Policy",
|
|
40
|
+
recommendation: "Set to strict-origin-when-cross-origin or no-referrer",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
header: "permissions-policy",
|
|
44
|
+
name: "Permissions-Policy",
|
|
45
|
+
recommendation: "Restrict browser features with Permissions-Policy",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
header: "x-xss-protection",
|
|
49
|
+
name: "X-XSS-Protection",
|
|
50
|
+
recommendation: "Set to 1; mode=block (legacy protection for older browsers)",
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
for (const sh of securityHeaders) {
|
|
54
|
+
const value = get(headers, sh.header);
|
|
55
|
+
const present = value !== null;
|
|
56
|
+
if (present)
|
|
57
|
+
score++;
|
|
58
|
+
checks.push({
|
|
59
|
+
header: sh.name,
|
|
60
|
+
present,
|
|
61
|
+
value,
|
|
62
|
+
recommendation: present ? "Present" : sh.recommendation,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
const pct = (score / maxScore) * 100;
|
|
66
|
+
let grade;
|
|
67
|
+
if (pct >= 85)
|
|
68
|
+
grade = "A";
|
|
69
|
+
else if (pct >= 70)
|
|
70
|
+
grade = "B";
|
|
71
|
+
else if (pct >= 50)
|
|
72
|
+
grade = "C";
|
|
73
|
+
else if (pct >= 30)
|
|
74
|
+
grade = "D";
|
|
75
|
+
else
|
|
76
|
+
grade = "F";
|
|
77
|
+
return { score, max_score: maxScore, grade, checks };
|
|
78
|
+
}
|
|
79
|
+
function analyzeCaching(headers) {
|
|
80
|
+
const cacheControl = get(headers, "cache-control");
|
|
81
|
+
const etag = get(headers, "etag");
|
|
82
|
+
const lastModified = get(headers, "last-modified");
|
|
83
|
+
const expires = get(headers, "expires");
|
|
84
|
+
const age = get(headers, "age");
|
|
85
|
+
const pragma = get(headers, "pragma");
|
|
86
|
+
const isNoCacheOrNoStore = cacheControl !== null &&
|
|
87
|
+
(cacheControl.includes("no-store") || cacheControl.includes("no-cache"));
|
|
88
|
+
const isCacheable = !isNoCacheOrNoStore && (cacheControl !== null || etag !== null || expires !== null);
|
|
89
|
+
let summary;
|
|
90
|
+
if (isNoCacheOrNoStore) {
|
|
91
|
+
summary = "Response is explicitly not cached";
|
|
92
|
+
}
|
|
93
|
+
else if (isCacheable) {
|
|
94
|
+
summary = "Response appears cacheable";
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
summary = "No explicit caching directives found";
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
cache_control: cacheControl,
|
|
101
|
+
etag,
|
|
102
|
+
last_modified: lastModified,
|
|
103
|
+
expires,
|
|
104
|
+
age,
|
|
105
|
+
pragma,
|
|
106
|
+
is_cacheable: isCacheable,
|
|
107
|
+
summary,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function analyzeCors(headers) {
|
|
111
|
+
const origin = get(headers, "access-control-allow-origin");
|
|
112
|
+
const methods = get(headers, "access-control-allow-methods");
|
|
113
|
+
const allowHeaders = get(headers, "access-control-allow-headers");
|
|
114
|
+
const credentials = get(headers, "access-control-allow-credentials");
|
|
115
|
+
const expose = get(headers, "access-control-expose-headers");
|
|
116
|
+
const maxAge = get(headers, "access-control-max-age");
|
|
117
|
+
const isOpen = origin === "*";
|
|
118
|
+
let summary;
|
|
119
|
+
if (origin === null) {
|
|
120
|
+
summary = "No CORS headers present";
|
|
121
|
+
}
|
|
122
|
+
else if (isOpen && credentials === "true") {
|
|
123
|
+
summary = "WARNING: Open CORS with credentials allowed is a security risk";
|
|
124
|
+
}
|
|
125
|
+
else if (isOpen) {
|
|
126
|
+
summary = "CORS is open to all origins";
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
summary = `CORS restricted to origin: ${origin}`;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
allow_origin: origin,
|
|
133
|
+
allow_methods: methods,
|
|
134
|
+
allow_headers: allowHeaders,
|
|
135
|
+
allow_credentials: credentials,
|
|
136
|
+
expose_headers: expose,
|
|
137
|
+
max_age: maxAge,
|
|
138
|
+
is_open: isOpen,
|
|
139
|
+
summary,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function analyzeCookies(headers) {
|
|
143
|
+
const cookies = [];
|
|
144
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
145
|
+
if (key.toLowerCase() !== "set-cookie")
|
|
146
|
+
continue;
|
|
147
|
+
const parts = value.split(";").map((p) => p.trim());
|
|
148
|
+
const nameValue = parts[0];
|
|
149
|
+
const eqIndex = nameValue.indexOf("=");
|
|
150
|
+
const name = eqIndex >= 0 ? nameValue.slice(0, eqIndex) : nameValue;
|
|
151
|
+
const flags = parts.slice(1).map((p) => p.toLowerCase());
|
|
152
|
+
const getFlag = (prefix) => {
|
|
153
|
+
const f = flags.find((fl) => fl.startsWith(prefix));
|
|
154
|
+
if (!f)
|
|
155
|
+
return null;
|
|
156
|
+
const idx = f.indexOf("=");
|
|
157
|
+
return idx >= 0 ? f.slice(idx + 1).trim() : "";
|
|
158
|
+
};
|
|
159
|
+
cookies.push({
|
|
160
|
+
raw: value,
|
|
161
|
+
name,
|
|
162
|
+
secure: flags.some((f) => f === "secure"),
|
|
163
|
+
http_only: flags.some((f) => f === "httponly"),
|
|
164
|
+
same_site: getFlag("samesite"),
|
|
165
|
+
path: getFlag("path"),
|
|
166
|
+
domain: getFlag("domain"),
|
|
167
|
+
expires: getFlag("expires"),
|
|
168
|
+
max_age: getFlag("max-age"),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return cookies;
|
|
172
|
+
}
|
|
173
|
+
function analyzeHeaders(headers) {
|
|
174
|
+
if (!headers || Object.keys(headers).length === 0) {
|
|
175
|
+
throw new Error("Provide a non-empty headers object to analyze");
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
raw_headers: headers,
|
|
179
|
+
security: analyzeSecurityHeaders(headers),
|
|
180
|
+
caching: analyzeCaching(headers),
|
|
181
|
+
cors: analyzeCors(headers),
|
|
182
|
+
cookies: analyzeCookies(headers),
|
|
183
|
+
server_info: {
|
|
184
|
+
server: get(headers, "server"),
|
|
185
|
+
powered_by: get(headers, "x-powered-by"),
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=header-analyzer.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface HttpRequestParams {
|
|
2
|
+
method: string;
|
|
3
|
+
url: string;
|
|
4
|
+
headers?: Record<string, string>;
|
|
5
|
+
body?: string;
|
|
6
|
+
timeout?: number;
|
|
7
|
+
follow_redirects?: boolean;
|
|
8
|
+
auth?: {
|
|
9
|
+
type: "basic" | "bearer";
|
|
10
|
+
username?: string;
|
|
11
|
+
password?: string;
|
|
12
|
+
token?: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface HttpRequestResult {
|
|
16
|
+
status: number;
|
|
17
|
+
status_text: string;
|
|
18
|
+
headers: Record<string, string>;
|
|
19
|
+
body: string;
|
|
20
|
+
timing: {
|
|
21
|
+
start_ms: number;
|
|
22
|
+
end_ms: number;
|
|
23
|
+
duration_ms: number;
|
|
24
|
+
};
|
|
25
|
+
redirected: boolean;
|
|
26
|
+
final_url: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function httpRequest(params: HttpRequestParams): Promise<HttpRequestResult>;
|
|
29
|
+
//# sourceMappingURL=http-request.d.ts.map
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.httpRequest = httpRequest;
|
|
4
|
+
async function httpRequest(params) {
|
|
5
|
+
const { method, url, headers = {}, body, timeout = 30000, follow_redirects = true, auth, } = params;
|
|
6
|
+
const upperMethod = method.toUpperCase();
|
|
7
|
+
const validMethods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
8
|
+
if (!validMethods.includes(upperMethod)) {
|
|
9
|
+
throw new Error(`Invalid HTTP method: ${method}. Supported: ${validMethods.join(", ")}`);
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
new URL(url);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
16
|
+
}
|
|
17
|
+
const requestHeaders = { ...headers };
|
|
18
|
+
if (auth) {
|
|
19
|
+
if (auth.type === "basic" && auth.username && auth.password) {
|
|
20
|
+
const encoded = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
|
|
21
|
+
requestHeaders["Authorization"] = `Basic ${encoded}`;
|
|
22
|
+
}
|
|
23
|
+
else if (auth.type === "bearer" && auth.token) {
|
|
24
|
+
requestHeaders["Authorization"] = `Bearer ${auth.token}`;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
29
|
+
const startMs = Date.now();
|
|
30
|
+
try {
|
|
31
|
+
const response = await fetch(url, {
|
|
32
|
+
method: upperMethod,
|
|
33
|
+
headers: requestHeaders,
|
|
34
|
+
body: upperMethod !== "GET" && upperMethod !== "HEAD" ? body : undefined,
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
redirect: follow_redirects ? "follow" : "manual",
|
|
37
|
+
});
|
|
38
|
+
const responseBody = await response.text();
|
|
39
|
+
const endMs = Date.now();
|
|
40
|
+
const responseHeaders = {};
|
|
41
|
+
response.headers.forEach((value, key) => {
|
|
42
|
+
responseHeaders[key] = value;
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
status: response.status,
|
|
46
|
+
status_text: response.statusText,
|
|
47
|
+
headers: responseHeaders,
|
|
48
|
+
body: responseBody.length > 50000
|
|
49
|
+
? responseBody.slice(0, 50000) + "\n...[truncated]"
|
|
50
|
+
: responseBody,
|
|
51
|
+
timing: {
|
|
52
|
+
start_ms: startMs,
|
|
53
|
+
end_ms: endMs,
|
|
54
|
+
duration_ms: endMs - startMs,
|
|
55
|
+
},
|
|
56
|
+
redirected: response.redirected,
|
|
57
|
+
final_url: response.url,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=http-request.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface JwtDecodeResult {
|
|
2
|
+
header: Record<string, unknown>;
|
|
3
|
+
payload: Record<string, unknown>;
|
|
4
|
+
signature_present: boolean;
|
|
5
|
+
issued_at: string | null;
|
|
6
|
+
expires_at: string | null;
|
|
7
|
+
not_before: string | null;
|
|
8
|
+
is_expired: boolean;
|
|
9
|
+
time_until_expiry: string | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function jwtDecode(token: string): JwtDecodeResult;
|
|
12
|
+
//# sourceMappingURL=jwt-decode.d.ts.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jwtDecode = jwtDecode;
|
|
4
|
+
function base64UrlDecode(input) {
|
|
5
|
+
let base64 = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
6
|
+
const padding = base64.length % 4;
|
|
7
|
+
if (padding === 2)
|
|
8
|
+
base64 += "==";
|
|
9
|
+
else if (padding === 3)
|
|
10
|
+
base64 += "=";
|
|
11
|
+
return Buffer.from(base64, "base64").toString("utf-8");
|
|
12
|
+
}
|
|
13
|
+
function formatDuration(ms) {
|
|
14
|
+
if (ms <= 0)
|
|
15
|
+
return "expired";
|
|
16
|
+
const seconds = Math.floor(ms / 1000);
|
|
17
|
+
const minutes = Math.floor(seconds / 60);
|
|
18
|
+
const hours = Math.floor(minutes / 60);
|
|
19
|
+
const days = Math.floor(hours / 24);
|
|
20
|
+
const parts = [];
|
|
21
|
+
if (days > 0)
|
|
22
|
+
parts.push(`${days}d`);
|
|
23
|
+
if (hours % 24 > 0)
|
|
24
|
+
parts.push(`${hours % 24}h`);
|
|
25
|
+
if (minutes % 60 > 0)
|
|
26
|
+
parts.push(`${minutes % 60}m`);
|
|
27
|
+
if (seconds % 60 > 0 && days === 0)
|
|
28
|
+
parts.push(`${seconds % 60}s`);
|
|
29
|
+
return parts.join(" ") || "0s";
|
|
30
|
+
}
|
|
31
|
+
function timestampToIso(ts) {
|
|
32
|
+
if (typeof ts !== "number")
|
|
33
|
+
return null;
|
|
34
|
+
return new Date(ts * 1000).toISOString();
|
|
35
|
+
}
|
|
36
|
+
function jwtDecode(token) {
|
|
37
|
+
const trimmed = token.trim();
|
|
38
|
+
const parts = trimmed.split(".");
|
|
39
|
+
if (parts.length < 2 || parts.length > 3) {
|
|
40
|
+
throw new Error(`Invalid JWT: expected 2 or 3 parts separated by dots, got ${parts.length}`);
|
|
41
|
+
}
|
|
42
|
+
let header;
|
|
43
|
+
let payload;
|
|
44
|
+
try {
|
|
45
|
+
header = JSON.parse(base64UrlDecode(parts[0]));
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new Error("Invalid JWT: unable to decode header");
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
payload = JSON.parse(base64UrlDecode(parts[1]));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error("Invalid JWT: unable to decode payload");
|
|
55
|
+
}
|
|
56
|
+
const now = Date.now();
|
|
57
|
+
const exp = typeof payload.exp === "number" ? payload.exp * 1000 : null;
|
|
58
|
+
const isExpired = exp !== null ? now > exp : false;
|
|
59
|
+
const timeUntilExpiry = exp !== null ? formatDuration(exp - now) : null;
|
|
60
|
+
return {
|
|
61
|
+
header,
|
|
62
|
+
payload,
|
|
63
|
+
signature_present: parts.length === 3 && parts[2].length > 0,
|
|
64
|
+
issued_at: timestampToIso(payload.iat),
|
|
65
|
+
expires_at: timestampToIso(payload.exp),
|
|
66
|
+
not_before: timestampToIso(payload.nbf),
|
|
67
|
+
is_expired: isExpired,
|
|
68
|
+
time_until_expiry: timeUntilExpiry,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=jwt-decode.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface ParsedUrl {
|
|
2
|
+
href: string;
|
|
3
|
+
protocol: string;
|
|
4
|
+
host: string;
|
|
5
|
+
hostname: string;
|
|
6
|
+
port: string;
|
|
7
|
+
pathname: string;
|
|
8
|
+
search: string;
|
|
9
|
+
query_params: Record<string, string | string[]>;
|
|
10
|
+
hash: string;
|
|
11
|
+
origin: string;
|
|
12
|
+
username: string;
|
|
13
|
+
password: string;
|
|
14
|
+
}
|
|
15
|
+
export interface BuildUrlParams {
|
|
16
|
+
protocol?: string;
|
|
17
|
+
hostname: string;
|
|
18
|
+
port?: string | number;
|
|
19
|
+
pathname?: string;
|
|
20
|
+
query_params?: Record<string, string | string[]>;
|
|
21
|
+
hash?: string;
|
|
22
|
+
username?: string;
|
|
23
|
+
password?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function parseUrl(url: string): ParsedUrl;
|
|
26
|
+
export declare function buildUrl(params: BuildUrlParams): string;
|
|
27
|
+
//# sourceMappingURL=url-parser.d.ts.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseUrl = parseUrl;
|
|
4
|
+
exports.buildUrl = buildUrl;
|
|
5
|
+
function parseUrl(url) {
|
|
6
|
+
let parsed;
|
|
7
|
+
try {
|
|
8
|
+
parsed = new URL(url);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
12
|
+
}
|
|
13
|
+
const queryParams = {};
|
|
14
|
+
parsed.searchParams.forEach((value, key) => {
|
|
15
|
+
const existing = queryParams[key];
|
|
16
|
+
if (existing === undefined) {
|
|
17
|
+
queryParams[key] = value;
|
|
18
|
+
}
|
|
19
|
+
else if (Array.isArray(existing)) {
|
|
20
|
+
existing.push(value);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
queryParams[key] = [existing, value];
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
href: parsed.href,
|
|
28
|
+
protocol: parsed.protocol,
|
|
29
|
+
host: parsed.host,
|
|
30
|
+
hostname: parsed.hostname,
|
|
31
|
+
port: parsed.port,
|
|
32
|
+
pathname: parsed.pathname,
|
|
33
|
+
search: parsed.search,
|
|
34
|
+
query_params: queryParams,
|
|
35
|
+
hash: parsed.hash,
|
|
36
|
+
origin: parsed.origin,
|
|
37
|
+
username: parsed.username,
|
|
38
|
+
password: parsed.password,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function buildUrl(params) {
|
|
42
|
+
const { protocol = "https:", hostname, port, pathname = "/", query_params, hash, username, password, } = params;
|
|
43
|
+
if (!hostname) {
|
|
44
|
+
throw new Error("hostname is required to build a URL");
|
|
45
|
+
}
|
|
46
|
+
const proto = protocol.endsWith(":") ? protocol : protocol + ":";
|
|
47
|
+
const url = new URL(`${proto}//${hostname}`);
|
|
48
|
+
if (port !== undefined && port !== "") {
|
|
49
|
+
url.port = String(port);
|
|
50
|
+
}
|
|
51
|
+
url.pathname = pathname;
|
|
52
|
+
if (query_params) {
|
|
53
|
+
for (const [key, value] of Object.entries(query_params)) {
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
for (const v of value) {
|
|
56
|
+
url.searchParams.append(key, v);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
url.searchParams.set(key, value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (hash) {
|
|
65
|
+
url.hash = hash.startsWith("#") ? hash : `#${hash}`;
|
|
66
|
+
}
|
|
67
|
+
if (username)
|
|
68
|
+
url.username = username;
|
|
69
|
+
if (password)
|
|
70
|
+
url.password = password;
|
|
71
|
+
return url.href;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=url-parser.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rog0x/mcp-api-tools",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "HTTP/API testing tools for AI agents via MCP",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"mcp",
|
|
13
|
+
"api",
|
|
14
|
+
"http",
|
|
15
|
+
"testing",
|
|
16
|
+
"tools"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.15.3",
|
|
24
|
+
"typescript": "^5.8.3"
|
|
25
|
+
}
|
|
26
|
+
}
|