viafrei 0.0.2 → 0.0.9
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/CHANGELOG.md +230 -0
- package/LICENSE +218 -0
- package/NOTICE +32 -0
- package/README.md +194 -1
- package/SOURCES.md +454 -0
- package/dist/bridge.d.ts +23 -0
- package/dist/bridge.js +242 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +113 -0
- package/dist/config.d.ts +59 -0
- package/dist/config.js +177 -0
- package/dist/failure.d.ts +61 -0
- package/dist/failure.js +186 -0
- package/dist/fetch.d.ts +24 -0
- package/dist/fetch.js +120 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +11 -0
- package/dist/version.d.ts +5 -0
- package/dist/version.js +23 -0
- package/package.json +64 -5
- package/index.js +0 -2
package/dist/failure.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { StreamableHTTPError } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
2
|
+
import { EXIT } from './config.js';
|
|
3
|
+
const STATUS_TEXT = {
|
|
4
|
+
400: 'Bad Request',
|
|
5
|
+
401: 'Unauthorized',
|
|
6
|
+
403: 'Forbidden',
|
|
7
|
+
404: 'Not Found',
|
|
8
|
+
405: 'Method Not Allowed',
|
|
9
|
+
406: 'Not Acceptable',
|
|
10
|
+
408: 'Request Timeout',
|
|
11
|
+
410: 'Gone',
|
|
12
|
+
413: 'Payload Too Large',
|
|
13
|
+
415: 'Unsupported Media Type',
|
|
14
|
+
429: 'Too Many Requests',
|
|
15
|
+
500: 'Internal Server Error',
|
|
16
|
+
502: 'Bad Gateway',
|
|
17
|
+
503: 'Service Unavailable',
|
|
18
|
+
504: 'Gateway Timeout'
|
|
19
|
+
};
|
|
20
|
+
const SYSCALL_TEXT = {
|
|
21
|
+
ECONNREFUSED: 'connection refused',
|
|
22
|
+
ENOTFOUND: 'host not found (DNS)',
|
|
23
|
+
EAI_AGAIN: 'DNS lookup failed',
|
|
24
|
+
ECONNRESET: 'connection reset by peer',
|
|
25
|
+
EHOSTUNREACH: 'host unreachable',
|
|
26
|
+
ENETUNREACH: 'network unreachable',
|
|
27
|
+
ETIMEDOUT: 'connection timed out',
|
|
28
|
+
EPIPE: 'connection closed while writing',
|
|
29
|
+
CERT_HAS_EXPIRED: 'the TLS certificate has expired',
|
|
30
|
+
UNABLE_TO_VERIFY_LEAF_SIGNATURE: 'the TLS certificate could not be verified',
|
|
31
|
+
DEPTH_ZERO_SELF_SIGNED_CERT: 'the TLS certificate is self-signed'
|
|
32
|
+
};
|
|
33
|
+
/** Collapse anything to a single readable line. */
|
|
34
|
+
function oneLine(text, limit = 200) {
|
|
35
|
+
const flattened = text.replace(/\s+/gu, ' ').trim();
|
|
36
|
+
return flattened.length > limit ? `${flattened.slice(0, limit - 1)}…` : flattened;
|
|
37
|
+
}
|
|
38
|
+
function errorCode(error) {
|
|
39
|
+
let current = error;
|
|
40
|
+
for (let depth = 0; depth < 5 && current !== null && typeof current === 'object'; depth += 1) {
|
|
41
|
+
const code = current.code;
|
|
42
|
+
if (typeof code === 'string') {
|
|
43
|
+
return code;
|
|
44
|
+
}
|
|
45
|
+
current = current.cause;
|
|
46
|
+
}
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
/** True when the failure is worth exactly one more attempt. */
|
|
50
|
+
export function isRetryable(error) {
|
|
51
|
+
if (error instanceof StreamableHTTPError) {
|
|
52
|
+
return error.code === 502 || error.code === 503 || error.code === 504;
|
|
53
|
+
}
|
|
54
|
+
const code = errorCode(error);
|
|
55
|
+
if (code === undefined) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return ['ECONNRESET', 'ETIMEDOUT', 'EAI_AGAIN', 'EPIPE', 'UND_ERR_SOCKET', 'UND_ERR_CONNECT_TIMEOUT'].includes(code);
|
|
59
|
+
}
|
|
60
|
+
/** Raised by the fetch wrapper when our own timeout fired. */
|
|
61
|
+
export class RequestTimeoutError extends Error {
|
|
62
|
+
timeoutMs;
|
|
63
|
+
constructor(timeoutMs) {
|
|
64
|
+
super(`no answer within ${timeoutMs} ms`);
|
|
65
|
+
this.timeoutMs = timeoutMs;
|
|
66
|
+
this.name = 'RequestTimeoutError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* How many same-origin hops are a redirect, and how many are a loop.
|
|
71
|
+
*
|
|
72
|
+
* Defined here, next to the sentence that quotes it, and imported by the fetch
|
|
73
|
+
* wrapper that enforces it. It used to be defined in `src/fetch.ts` and typed
|
|
74
|
+
* out again as a literal in `redirectLine()`, which is a number in two places
|
|
75
|
+
* and therefore a number that can disagree with itself.
|
|
76
|
+
*/
|
|
77
|
+
export const MAX_REDIRECTS = 5;
|
|
78
|
+
/**
|
|
79
|
+
* Raised by the fetch wrapper when a redirect was not followed.
|
|
80
|
+
*
|
|
81
|
+
* Every request carries the caller's `--header` values. A redirect is the
|
|
82
|
+
* server choosing where those go next, so the choice is made here instead: one
|
|
83
|
+
* origin is one trust boundary, and crossing it is refused out loud rather than
|
|
84
|
+
* followed quietly.
|
|
85
|
+
*/
|
|
86
|
+
export class RedirectRefusedError extends Error {
|
|
87
|
+
reason;
|
|
88
|
+
from;
|
|
89
|
+
to;
|
|
90
|
+
httpStatus;
|
|
91
|
+
constructor(detail) {
|
|
92
|
+
super(`redirect not followed (${detail.reason})`);
|
|
93
|
+
this.name = 'RedirectRefusedError';
|
|
94
|
+
this.reason = detail.reason;
|
|
95
|
+
this.from = detail.from;
|
|
96
|
+
this.to = detail.to;
|
|
97
|
+
this.httpStatus = detail.status;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Turn any thrown value into one line plus an exit code.
|
|
102
|
+
*
|
|
103
|
+
* Three outcomes, and the distinction is the point: the endpoint answered and
|
|
104
|
+
* said no (REFUSED), the endpoint never answered (UNREACHABLE), or something
|
|
105
|
+
* else entirely (UNEXPECTED).
|
|
106
|
+
*/
|
|
107
|
+
export function describeFailure(error, url) {
|
|
108
|
+
if (error instanceof RedirectRefusedError) {
|
|
109
|
+
return { line: redirectLine(error), exitCode: EXIT.REFUSED };
|
|
110
|
+
}
|
|
111
|
+
if (error instanceof StreamableHTTPError && error.code !== undefined && error.code < 0) {
|
|
112
|
+
// The SDK uses a negative code for "the endpoint answered, but not with
|
|
113
|
+
// MCP" - a login page, an HTML error, a proxy's own 200. There is no
|
|
114
|
+
// HTTP status here, so printing one (`HTTP -1 HTTP error`) invented a
|
|
115
|
+
// fact, and "refused" was wrong twice over: it answered, and it did not
|
|
116
|
+
// refuse.
|
|
117
|
+
return {
|
|
118
|
+
line: `viafrei: ${url} answered, but not with MCP - ${oneLine(stripSdkPrefix(error.message), 120)}. Is that the Streamable-HTTP endpoint (usually .../mcp), or is something in front of it answering instead?`,
|
|
119
|
+
exitCode: EXIT.REFUSED
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (error instanceof StreamableHTTPError && error.code !== undefined) {
|
|
123
|
+
const status = error.code;
|
|
124
|
+
const name = STATUS_TEXT[status] ?? 'HTTP error';
|
|
125
|
+
const detail = extractServerDetail(error.message);
|
|
126
|
+
const suffix = detail === undefined ? '' : ` - ${detail}`;
|
|
127
|
+
return {
|
|
128
|
+
line: `viafrei: ${url} refused the request: HTTP ${status} ${name}${suffix}`,
|
|
129
|
+
exitCode: EXIT.REFUSED,
|
|
130
|
+
status
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (error instanceof RequestTimeoutError) {
|
|
134
|
+
return {
|
|
135
|
+
line: `viafrei: ${url} did not answer within ${error.timeoutMs} ms - the endpoint may be down, or --timeout is too short`,
|
|
136
|
+
exitCode: EXIT.UNREACHABLE
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const code = errorCode(error);
|
|
140
|
+
if (code !== undefined) {
|
|
141
|
+
const explanation = SYSCALL_TEXT[code] ?? code;
|
|
142
|
+
return {
|
|
143
|
+
line: `viafrei: cannot reach ${url}: ${explanation} (${code}) - check your network connection; --url only if you relay through a proxy`,
|
|
144
|
+
exitCode: EXIT.UNREACHABLE
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
if (/fetch failed|network|socket/iu.test(message)) {
|
|
149
|
+
return {
|
|
150
|
+
line: `viafrei: cannot reach ${url}: ${oneLine(message)}`,
|
|
151
|
+
exitCode: EXIT.UNREACHABLE
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
line: `viafrei: ${url}: ${oneLine(message)}`,
|
|
156
|
+
exitCode: EXIT.UNEXPECTED
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* The SDK wraps the response body into the error message. Keep a short, single
|
|
161
|
+
* line of it - it is often the only thing that says *why* - and drop the rest.
|
|
162
|
+
*/
|
|
163
|
+
function extractServerDetail(message) {
|
|
164
|
+
const marker = 'Error POSTing to endpoint:';
|
|
165
|
+
const index = message.indexOf(marker);
|
|
166
|
+
const body = index === -1 ? message : message.slice(index + marker.length);
|
|
167
|
+
const trimmed = oneLine(body, 120);
|
|
168
|
+
if (trimmed === '' || trimmed === 'null' || trimmed === 'undefined') {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
return trimmed;
|
|
172
|
+
}
|
|
173
|
+
/** One line for a redirect we chose not to follow, naming both ends. */
|
|
174
|
+
function redirectLine(error) {
|
|
175
|
+
if (error.reason === 'cross-origin') {
|
|
176
|
+
return `viafrei: ${error.from} answered HTTP ${error.httpStatus} redirecting to ${error.to} - a different origin, and this request carries the headers you gave me, so I did not follow it. Point --url at the final endpoint if that redirect is expected.`;
|
|
177
|
+
}
|
|
178
|
+
if (error.reason === 'too-many') {
|
|
179
|
+
return `viafrei: ${error.from} kept redirecting (more than ${MAX_REDIRECTS} hops, last ${error.to}) - that is a loop, not an endpoint.`;
|
|
180
|
+
}
|
|
181
|
+
return `viafrei: ${error.from} answered HTTP ${error.httpStatus} - a redirect with no Location to follow.`;
|
|
182
|
+
}
|
|
183
|
+
/** The SDK prefixes its own message; the user does not need our plumbing. */
|
|
184
|
+
function stripSdkPrefix(message) {
|
|
185
|
+
return message.replace(/^Streamable HTTP error:\s*/u, '');
|
|
186
|
+
}
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { FetchLike } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
2
|
+
/**
|
|
3
|
+
* `fetch` with a timeout, exactly one retry, and an explicit redirect policy.
|
|
4
|
+
*
|
|
5
|
+
* Three deliberate decisions, all because getting them wrong is worse than the
|
|
6
|
+
* feature:
|
|
7
|
+
*
|
|
8
|
+
* - **The event stream is never timed out.** The standalone `GET` is a
|
|
9
|
+
* long-lived stream by design; a 30-second deadline on it would look like a
|
|
10
|
+
* flaky server every 30 seconds.
|
|
11
|
+
* - **A timeout is not retried.** One retry after a timeout doubles the worst
|
|
12
|
+
* case and hides a slow endpoint behind a longer wait. Retry covers the
|
|
13
|
+
* momentary failures (a reset socket, a 503 from a proxy in front of the
|
|
14
|
+
* server), not a server that is simply not answering.
|
|
15
|
+
* - **Redirects are followed by hand, and only within one origin.** Every
|
|
16
|
+
* request here carries whatever `--header` the user gave us, which is how an
|
|
17
|
+
* API key gets to the server. `redirect: 'follow'` would hand those headers
|
|
18
|
+
* to whatever the `Location` says - `fetch` drops `Authorization` across
|
|
19
|
+
* origins, but it does not drop `X-Api-Key`, and a server that can answer a
|
|
20
|
+
* redirect can choose the origin. So the redirect is resolved here: same
|
|
21
|
+
* origin is followed with the headers, a different origin is refused with one
|
|
22
|
+
* line naming both. Nobody's key travels somewhere they did not point it.
|
|
23
|
+
*/
|
|
24
|
+
export declare function createFetch(timeoutMs: number): FetchLike;
|
package/dist/fetch.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { MAX_REDIRECTS, RedirectRefusedError, RequestTimeoutError, isRetryable } from './failure.js';
|
|
2
|
+
/** Statuses that mean "the hop in front of the server had a moment". */
|
|
3
|
+
const RETRY_STATUS = new Set([502, 503, 504]);
|
|
4
|
+
/** How long to wait before the single retry. */
|
|
5
|
+
const RETRY_DELAY_MS = 250;
|
|
6
|
+
const REDIRECT_STATUS = new Set([301, 302, 303, 307, 308]);
|
|
7
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
8
|
+
const originOf = (url) => new URL(url).origin;
|
|
9
|
+
/**
|
|
10
|
+
* `fetch` with a timeout, exactly one retry, and an explicit redirect policy.
|
|
11
|
+
*
|
|
12
|
+
* Three deliberate decisions, all because getting them wrong is worse than the
|
|
13
|
+
* feature:
|
|
14
|
+
*
|
|
15
|
+
* - **The event stream is never timed out.** The standalone `GET` is a
|
|
16
|
+
* long-lived stream by design; a 30-second deadline on it would look like a
|
|
17
|
+
* flaky server every 30 seconds.
|
|
18
|
+
* - **A timeout is not retried.** One retry after a timeout doubles the worst
|
|
19
|
+
* case and hides a slow endpoint behind a longer wait. Retry covers the
|
|
20
|
+
* momentary failures (a reset socket, a 503 from a proxy in front of the
|
|
21
|
+
* server), not a server that is simply not answering.
|
|
22
|
+
* - **Redirects are followed by hand, and only within one origin.** Every
|
|
23
|
+
* request here carries whatever `--header` the user gave us, which is how an
|
|
24
|
+
* API key gets to the server. `redirect: 'follow'` would hand those headers
|
|
25
|
+
* to whatever the `Location` says - `fetch` drops `Authorization` across
|
|
26
|
+
* origins, but it does not drop `X-Api-Key`, and a server that can answer a
|
|
27
|
+
* redirect can choose the origin. So the redirect is resolved here: same
|
|
28
|
+
* origin is followed with the headers, a different origin is refused with one
|
|
29
|
+
* line naming both. Nobody's key travels somewhere they did not point it.
|
|
30
|
+
*/
|
|
31
|
+
export function createFetch(timeoutMs) {
|
|
32
|
+
return async (url, init) => {
|
|
33
|
+
const method = (init?.method ?? 'GET').toUpperCase();
|
|
34
|
+
const isEventStream = method === 'GET';
|
|
35
|
+
/** One request, no redirect handling, with our timeout attached. */
|
|
36
|
+
const once = async (target, requestInit) => {
|
|
37
|
+
const withPolicy = { ...requestInit, redirect: 'manual' };
|
|
38
|
+
if (isEventStream) {
|
|
39
|
+
return fetch(target, withPolicy);
|
|
40
|
+
}
|
|
41
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
42
|
+
const signals = [timeout];
|
|
43
|
+
if (requestInit?.signal) {
|
|
44
|
+
signals.push(requestInit.signal);
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return await fetch(target, { ...withPolicy, signal: AbortSignal.any(signals) });
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
if (timeout.aborted && requestInit?.signal?.aborted !== true) {
|
|
51
|
+
throw new RequestTimeoutError(timeoutMs);
|
|
52
|
+
}
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/** One request plus any same-origin redirects it asks for. */
|
|
57
|
+
const attempt = async () => {
|
|
58
|
+
let target = String(url);
|
|
59
|
+
let requestInit = init;
|
|
60
|
+
for (let hop = 0; hop <= MAX_REDIRECTS; hop += 1) {
|
|
61
|
+
const response = await once(target, requestInit);
|
|
62
|
+
if (!REDIRECT_STATUS.has(response.status)) {
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
65
|
+
const location = response.headers.get('location');
|
|
66
|
+
await response.body?.cancel().catch(() => undefined);
|
|
67
|
+
if (location === null || location.trim() === '') {
|
|
68
|
+
throw new RedirectRefusedError({
|
|
69
|
+
reason: 'no-location',
|
|
70
|
+
from: target,
|
|
71
|
+
to: '(none)',
|
|
72
|
+
status: response.status
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const next = new URL(location, target);
|
|
76
|
+
if (next.origin !== originOf(target)) {
|
|
77
|
+
throw new RedirectRefusedError({
|
|
78
|
+
reason: 'cross-origin',
|
|
79
|
+
from: target,
|
|
80
|
+
to: next.origin,
|
|
81
|
+
status: response.status
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Same origin: the headers are already meant for this server, so
|
|
85
|
+
// following is safe. 303 (and the historical 301/302 on a POST)
|
|
86
|
+
// means "ask again with GET, without the body".
|
|
87
|
+
const dropsBody = response.status === 303 || ((response.status === 301 || response.status === 302) && method !== 'GET');
|
|
88
|
+
if (dropsBody) {
|
|
89
|
+
const { body: _body, ...rest } = requestInit ?? {};
|
|
90
|
+
requestInit = { ...rest, method: 'GET' };
|
|
91
|
+
}
|
|
92
|
+
target = next.toString();
|
|
93
|
+
}
|
|
94
|
+
throw new RedirectRefusedError({
|
|
95
|
+
reason: 'too-many',
|
|
96
|
+
from: String(url),
|
|
97
|
+
to: target,
|
|
98
|
+
status: 0
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
const canRetry = () => !isEventStream && init?.signal?.aborted !== true;
|
|
102
|
+
let response;
|
|
103
|
+
try {
|
|
104
|
+
response = await attempt();
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (canRetry() && isRetryable(error)) {
|
|
108
|
+
await sleep(RETRY_DELAY_MS);
|
|
109
|
+
return attempt();
|
|
110
|
+
}
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
if (canRetry() && RETRY_STATUS.has(response.status)) {
|
|
114
|
+
await response.body?.cancel().catch(() => undefined);
|
|
115
|
+
await sleep(RETRY_DELAY_MS);
|
|
116
|
+
return attempt();
|
|
117
|
+
}
|
|
118
|
+
return response;
|
|
119
|
+
};
|
|
120
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic entry point.
|
|
3
|
+
*
|
|
4
|
+
* The bridge is a command-line tool first (`npx viafrei`), but a client that
|
|
5
|
+
* wants to embed the relay - a desktop app shipping its own supervisor, say -
|
|
6
|
+
* should not have to spawn a process to do it.
|
|
7
|
+
*/
|
|
8
|
+
export { startBridge, type BridgeHandle, type BridgeHooks } from './bridge.js';
|
|
9
|
+
export { DEFAULT_MCP_ORIGIN, DEFAULT_MCP_PATH, DEFAULT_MCP_URL, DEFAULT_TIMEOUT_MS, EXIT, TIMEOUT_ENV_VAR, URL_ENV_VAR, UsageError, helpText, parseOptions, type ExitCode, type Options } from './config.js';
|
|
10
|
+
export { describeFailure, RequestTimeoutError, type Failure } from './failure.js';
|
|
11
|
+
export { packageVersion } from './version.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Programmatic entry point.
|
|
3
|
+
*
|
|
4
|
+
* The bridge is a command-line tool first (`npx viafrei`), but a client that
|
|
5
|
+
* wants to embed the relay - a desktop app shipping its own supervisor, say -
|
|
6
|
+
* should not have to spawn a process to do it.
|
|
7
|
+
*/
|
|
8
|
+
export { startBridge } from './bridge.js';
|
|
9
|
+
export { DEFAULT_MCP_ORIGIN, DEFAULT_MCP_PATH, DEFAULT_MCP_URL, DEFAULT_TIMEOUT_MS, EXIT, TIMEOUT_ENV_VAR, URL_ENV_VAR, UsageError, helpText, parseOptions } from './config.js';
|
|
10
|
+
export { describeFailure, RequestTimeoutError } from './failure.js';
|
|
11
|
+
export { packageVersion } from './version.js';
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* The version printed by `--version`, read from the package manifest that ships
|
|
4
|
+
* beside `dist/`. One number, one source: no constant to forget to bump.
|
|
5
|
+
*/
|
|
6
|
+
export function packageVersion() {
|
|
7
|
+
for (const relative of ['../package.json', '../../package.json']) {
|
|
8
|
+
try {
|
|
9
|
+
const raw = readFileSync(new URL(relative, import.meta.url), 'utf8');
|
|
10
|
+
const parsed = JSON.parse(raw);
|
|
11
|
+
if (parsed !== null && typeof parsed === 'object') {
|
|
12
|
+
const { name, version } = parsed;
|
|
13
|
+
if (name === 'viafrei' && typeof version === 'string') {
|
|
14
|
+
return version;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// Try the next candidate.
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return '0.0.0-unknown';
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viafrei",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
"version": "0.0.9",
|
|
4
|
+
"description": "stdio<->Streamable-HTTP bridge for the ViaFrei MCP server - German road, rail, parking, charging and fuel data for stdio-only MCP clients",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"modelcontextprotocol",
|
|
8
|
+
"stdio",
|
|
9
|
+
"bridge",
|
|
10
|
+
"proxy",
|
|
11
|
+
"germany",
|
|
12
|
+
"transport",
|
|
13
|
+
"open-data"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://viafrei.de",
|
|
16
|
+
"bugs": "https://github.com/mavrovde/viafrei-bridge/issues",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/mavrovde/viafrei-bridge.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "Apache-2.0",
|
|
22
|
+
"author": "ViaFrei",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"bin": {
|
|
25
|
+
"viafrei": "dist/cli.js"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
|
+
"default": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE",
|
|
39
|
+
"NOTICE",
|
|
40
|
+
"SOURCES.md",
|
|
41
|
+
"CHANGELOG.md"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=22"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public",
|
|
48
|
+
"provenance": true
|
|
49
|
+
},
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsc -p tsconfig.json",
|
|
52
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true});require('node:fs').rmSync('.test-build',{recursive:true,force:true})\"",
|
|
53
|
+
"test": "npm run build && tsc -p tsconfig.test.json && node --test --test-timeout=30000 .test-build/test/*.test.js",
|
|
54
|
+
"test:gate": "node scripts/check-tarball.test.mjs",
|
|
55
|
+
"test:leaks": "node scripts/check-leaks.test.mjs",
|
|
56
|
+
"check:tarball": "node scripts/check-tarball.mjs",
|
|
57
|
+
"check:leaks": "node scripts/check-leaks.mjs",
|
|
58
|
+
"rules:show": "node scripts/show-rules.mjs"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@modelcontextprotocol/sdk": "^1.30.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@types/node": "^22.15.0",
|
|
65
|
+
"typescript": "^5.9.0"
|
|
66
|
+
}
|
|
8
67
|
}
|
package/index.js
DELETED