@prereason/mcp 0.3.2 → 0.5.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/CHANGELOG.md +36 -0
- package/README.md +64 -48
- package/bin/cli.js +97 -49
- package/lib/claim.js +233 -0
- package/lib/credentials.js +152 -0
- package/lib/jsonrpc.js +60 -0
- package/lib/sse.js +170 -0
- package/lib/stdio.js +149 -0
- package/lib/streamable-http.js +302 -0
- package/package.json +7 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.0 (2026-09-21)
|
|
4
|
+
|
|
5
|
+
### Removed
|
|
6
|
+
- `@modelcontextprotocol/sdk`, the only dependency. The bridge now installs nothing. It used the SDK for two classes, `StdioServerTransport` and `StreamableHTTPClientTransport`, and paid 91 packages and 25 MB on disk for them: Express, Hono, CORS, `express-rate-limit`, `jose`, `pkce-challenge`, `ajv`, `zod` and the rest are the SDK server and OAuth halves, and a relay calls none of them. The published tarball is 23 KB, `npm ls` is one line, and the code a security review has to read is the code in this repository.
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `lib/stdio.js`: newline delimited JSON-RPC over stdin and stdout, with the SDK framing kept exactly, including the 10 MB line ceiling, the carriage return before the newline, and the rule that a line which will not parse is reported and skipped rather than ending the session.
|
|
10
|
+
- `lib/streamable-http.js`: the Streamable HTTP client. POST a frame, read the answer as JSON or as an event stream, carry `Mcp-Session-Id` onto later requests, open the optional GET stream once the session is initialized, and treat a 405 there as the server saying it has none, which is what PreReason answers. Headers are read from `requestInit` on every request rather than copied once, because the claim flow attaches `Authorization` to that same object after the transport has started.
|
|
11
|
+
- `lib/sse.js`: a Server-Sent Events decoder following the same buffering rules as `eventsource-parser`, including the carriage return split across two chunks that would otherwise turn one event into two.
|
|
12
|
+
- `lib/jsonrpc.js`: the shape check that replaces the SDK zod schema. A relay has no reason to validate methods or params, only to be sure it is not writing a bare string to a host that would treat the stream as corrupt.
|
|
13
|
+
- `test/` grows to 71 cases across six files, among them `test/bridge.test.js`, which spawns `bin/cli.js` against a stub server and asserts on the headers that reach the wire. The suite runs on an empty `node_modules`, which is the proof there is nothing left to install.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `socket.yml` describes this package own code and nothing else, and says in the file what it is for: it configures the Socket GitHub app and CLI for this repository, and has no effect on the public package score.
|
|
17
|
+
- `server.json` registry version 0.8.2, npm package 0.5.0.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## 0.4.0 (2026-09-21)
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- The bridge gets its own key. With no `PREREASON_API_KEY` and no saved key it asks PreReason for access, prints one link to stderr (`Open https://www.prereason.com/claim/PR-XXXX-XXXX to approve access`), keeps serving the free tools, and polls until the person approves. The key arrives once, is saved to `~/.prereason/credentials.json` (0700 directory, 0600 file on POSIX; Windows has no mode bits), and is attached to the running connection without a restart. While the link is pending, any `AUTH_REQUIRED` tool result starts with `Approve at <link>` so the assistant can relay it, because a person inside Claude Desktop never sees this process's stderr.
|
|
24
|
+
- `--login` (ask for access now, save the key, exit), `--logout` (forget the saved key), `--credentials-file <path>` and `PREREASON_CREDENTIALS_FILE`.
|
|
25
|
+
- `PREREASON_CLIENT` (for example `claude-desktop`) is forwarded as `X-PreReason-Client` so the dashboard names the connection; every request carries `User-Agent: prereason-mcp/0.4.0`.
|
|
26
|
+
- `node --test` suite under `test/`: key precedence, file modes, the poll loop against a stubbed server, and that a claim token never touches the disk.
|
|
27
|
+
- `mcpb/manifest.json`: the Claude Desktop extension manifest for a single click install (`npx @anthropic-ai/mcpb pack`), with the key optional.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
- Key precedence is documented and tested: `PREREASON_API_KEY`, then `--header`, then the credentials file, then the claim flow.
|
|
31
|
+
- `@modelcontextprotocol/sdk` 1.27.1 to 1.30.0.
|
|
32
|
+
- The version is one number again: `cli.js` printed 0.3.1 while `package.json` said 0.3.2 and `server.json` 0.3.1.
|
|
33
|
+
- `server.json` (the registry entry) returns to `com.prereason/mcp`, the name that has been live in the registry since March; the July rename to `io.github.PreReason/mcp` was never published.
|
|
34
|
+
- `server.json` names the `X-API-Key` header on the remote, the header the published record already declares. The server accepts `Authorization: Bearer` as well. A registry client prompts a person for the header value, and with `X-API-Key` they paste the bare key: there is no `Bearer ` prefix to forget, and a key pasted without one into `Authorization` is silently treated as no key at all.
|
|
35
|
+
- `server.json` `description` is the one line every listing now carries, at 98 characters. It was 196, and the registry rejects anything over 100, so the record could not have been published.
|
|
36
|
+
- `server.json` `version` is 0.8.0 while the npm package stays 0.4.0. The registry marks a record latest only when its version sorts above the current latest, which is 0.7.2, so a record published as 0.4.0 would have been accepted and then ignored by everything downstream.
|
|
37
|
+
- README: the no key path comes first; OAuth is not offered until it is verified. The opening paragraph is the canonical directory copy.
|
|
38
|
+
|
|
3
39
|
## 0.3.2 (2026-08-19)
|
|
4
40
|
|
|
5
41
|
### Fixed
|
package/README.md
CHANGED
|
@@ -7,97 +7,91 @@
|
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
[](https://opensource.org/licenses/MIT)
|
|
9
9
|
[](https://glama.ai/mcp/servers/PreReason/mcp)
|
|
10
|
+
[](https://smithery.ai/servers/prereason/briefings)
|
|
10
11
|
|
|
11
|
-
**MCP server for [PreReason](https://www.prereason.com)
|
|
12
|
+
**MCP server for [PreReason](https://www.prereason.com).**
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
cross-asset regimes, and liquidity - each with trend signals, confidence scores, and causal narratives.
|
|
14
|
+
Bitcoin and macro market briefings for AI agents: trend signals, regimes, liquidity and ETF flows.
|
|
15
15
|
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
+
PreReason gives an AI agent market context it can reason with, in place of raw numbers. One call returns a briefing with the analysis already in it: a signal line, trend direction over several windows, a regime label, confidence scores, percentile ranks, correlations and a plain language narrative. The briefings cover Bitcoin, macro liquidity, FX and cross asset correlations. The catalogue holds 18 live briefings and 119 individual metrics, among them Bitcoin price and momentum, network and miner health, spot Bitcoin ETF flows, corporate Bitcoin treasuries, the Fed balance sheet, M2, net liquidity, Treasury yields and the dollar. It is served over MCP (a remote server and an npm bridge) and over REST, as Markdown or JSON. The catalogue tools need no key, and an agent can get a free key from inside the session: it shows one link, a person approves it, and the key arrives.
|
|
19
|
+
|
|
18
20
|
## Quick Start
|
|
19
21
|
|
|
20
|
-
### Option 1:
|
|
22
|
+
### Option 1: Claude Desktop, no key needed
|
|
23
|
+
|
|
24
|
+
**Requires [Node.js 18+](https://nodejs.org), and nothing else: the bridge has no dependencies.**
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
Add this to `claude_desktop_config.json` and restart Claude Desktop:
|
|
23
27
|
|
|
24
28
|
```json
|
|
25
29
|
{
|
|
26
30
|
"mcpServers": {
|
|
27
31
|
"prereason": {
|
|
28
|
-
"
|
|
29
|
-
"
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["-y", "@prereason/mcp"],
|
|
34
|
+
"env": { "PREREASON_CLIENT": "claude-desktop" }
|
|
30
35
|
}
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
38
|
```
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
The catalogue tools work at once. The first time a briefing needs a key, the bridge asks for access: ask Claude for any briefing and the answer starts with `Approve at https://www.prereason.com/claim/PR-XXXX-XXXX`. Open the link, sign in or create a free account, click Approve. The key arrives in the bridge on its own, is saved to `~/.prereason/credentials.json`, and the next call works. Nothing is created in your account until you click Approve.
|
|
36
41
|
|
|
37
|
-
|
|
42
|
+
Config file location:
|
|
43
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
44
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
45
|
+
- **Linux:** `~/.config/Claude/claude_desktop_config.json`
|
|
46
|
+
|
|
47
|
+
Already have a key? Add it to the `env` block as `"PREREASON_API_KEY": "pr_live_..."` and the bridge never asks.
|
|
38
48
|
|
|
39
|
-
|
|
49
|
+
### Option 2: Direct HTTP with an API key (Claude Code, Cursor, Windsurf, Codex, Gemini CLI, VS Code, scripts)
|
|
50
|
+
|
|
51
|
+
Clients that hold their own config can call the endpoint directly, with the key as a header:
|
|
40
52
|
|
|
41
53
|
```bash
|
|
42
|
-
# Claude Code
|
|
43
|
-
claude mcp add
|
|
54
|
+
# Claude Code
|
|
55
|
+
claude mcp add --transport http prereason https://api.prereason.com/api/mcp --header "Authorization: Bearer YOUR_API_KEY"
|
|
44
56
|
```
|
|
45
57
|
|
|
46
|
-
Or with an explicit key in your MCP config:
|
|
47
|
-
|
|
48
58
|
```json
|
|
49
59
|
{
|
|
50
60
|
"mcpServers": {
|
|
51
61
|
"prereason": {
|
|
52
62
|
"type": "http",
|
|
53
63
|
"url": "https://api.prereason.com/api/mcp",
|
|
54
|
-
"headers": {
|
|
55
|
-
"Authorization": "Bearer YOUR_API_KEY"
|
|
56
|
-
}
|
|
64
|
+
"headers": { "Authorization": "Bearer YOUR_API_KEY" }
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
67
|
}
|
|
60
68
|
```
|
|
61
69
|
|
|
62
|
-
|
|
70
|
+
Windsurf uses `serverUrl` instead of `url`; Gemini CLI uses `httpUrl`; Codex uses `url` plus `bearer_token_env_var` in `config.toml`. No key yet? Point the client at the endpoint without a header and ask the assistant to call `request_access`; it walks you through the same one link approval and shows the key once, which you then paste into the config.
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
### Option 3: Claude.ai and Claude Desktop custom connector
|
|
65
73
|
|
|
66
|
-
Add
|
|
74
|
+
[Add PreReason as a custom connector](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=PreReason&connectorUrl=https%3A%2F%2Fapi.prereason.com%2Fapi%2Fmcp), choose "No sign-in", and where the Request headers section is available add `Authorization` with the value `Bearer YOUR_API_KEY` (the word Bearer and the space are part of the value). Sign in support for connectors is being re-tested and is not offered until it is verified.
|
|
67
75
|
|
|
68
|
-
|
|
69
|
-
{
|
|
70
|
-
"mcpServers": {
|
|
71
|
-
"prereason": {
|
|
72
|
-
"command": "npx",
|
|
73
|
-
"args": ["-y", "@prereason/mcp"],
|
|
74
|
-
"env": {
|
|
75
|
-
"PREREASON_API_KEY": "YOUR_API_KEY"
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
```
|
|
76
|
+
## Get an API Key
|
|
81
77
|
|
|
82
|
-
|
|
83
|
-
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
84
|
-
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
78
|
+
Three ways, all free:
|
|
85
79
|
|
|
86
|
-
|
|
80
|
+
**From inside the session (any MCP client).** Ask your assistant to call `request_access`. It returns an `approve_url`; open it, sign in or create your account, click Approve. The assistant then calls `check_access` and receives the key once, attached to your account and named `Agent: <client_name>`.
|
|
87
81
|
|
|
88
|
-
|
|
82
|
+
**From code, for an agent with no browser.** `POST https://api.prereason.com/api/agent/claims` (no auth), show the human the `approve_url`, then poll `GET https://api.prereason.com/api/agent/claims/{claim_code}` with `Authorization: Bearer <claim_token>` until `status` is `approved`. Docs: [prereason.com/docs#agent-access](https://www.prereason.com/docs#agent-access).
|
|
89
83
|
|
|
90
|
-
|
|
91
|
-
2. Go to Dashboard > Settings > API Keys
|
|
92
|
-
3. Copy your key (starts with `pr_live_`)
|
|
84
|
+
**On the website.** Sign up at [prereason.com/signup](https://www.prereason.com/signup), then Dashboard > Settings > API Keys. Keys start with `pr_live_`.
|
|
93
85
|
|
|
94
|
-
##
|
|
86
|
+
## 7 MCP Tools
|
|
95
87
|
|
|
96
88
|
| Tool | Auth | Description |
|
|
97
89
|
|------|------|-------------|
|
|
98
90
|
| `list_briefings` | Open | List all 18 pre-reasoned market briefings with tier requirements |
|
|
99
|
-
| `list_metrics` | Open | List
|
|
91
|
+
| `list_metrics` | Open | List every available metric across bitcoin, macro, and calculated categories |
|
|
100
92
|
| `get_health` | Open | API health check, version, account tier |
|
|
93
|
+
| `request_access` | Open | Start getting a free key from inside the session; returns the approve link for the human |
|
|
94
|
+
| `check_access` | Open | Poll that claim; the first poll after approval returns the key once |
|
|
101
95
|
| `get_context` | Required | Fetch a pre-reasoned market briefing (markdown or JSON) |
|
|
102
96
|
| `get_metric` | Required | Fetch a single metric with trend/signal/percentile |
|
|
103
97
|
|
|
@@ -190,19 +184,41 @@ If your client supports remote HTTP servers, use [Quick Start Option 1](#option-
|
|
|
190
184
|
## CLI Usage
|
|
191
185
|
|
|
192
186
|
```bash
|
|
193
|
-
#
|
|
187
|
+
# No key: the bridge asks for access and prints one link to approve
|
|
188
|
+
npx @prereason/mcp
|
|
189
|
+
|
|
190
|
+
# Ask for access now, save the key, exit (useful before a first run)
|
|
191
|
+
npx @prereason/mcp --login
|
|
192
|
+
|
|
193
|
+
# Forget the saved key
|
|
194
|
+
npx @prereason/mcp --logout
|
|
195
|
+
|
|
196
|
+
# Use a key from the environment (never asks)
|
|
194
197
|
PREREASON_API_KEY=pr_live_... npx @prereason/mcp
|
|
195
198
|
|
|
196
|
-
#
|
|
197
|
-
npx @prereason/mcp
|
|
199
|
+
# Name the app the bridge runs in, so your dashboard names the connection
|
|
200
|
+
PREREASON_CLIENT=claude-desktop npx @prereason/mcp
|
|
198
201
|
|
|
199
|
-
#
|
|
202
|
+
# --header (backward compatible), a custom credentials file, a custom endpoint
|
|
203
|
+
npx @prereason/mcp --header "Authorization:Bearer YOUR_API_KEY"
|
|
204
|
+
npx @prereason/mcp --credentials-file /path/to/credentials.json
|
|
200
205
|
PREREASON_URL=https://custom.endpoint/mcp npx @prereason/mcp
|
|
201
206
|
|
|
202
|
-
# Help
|
|
203
207
|
npx @prereason/mcp --help
|
|
204
208
|
```
|
|
205
209
|
|
|
210
|
+
Key precedence: `PREREASON_API_KEY`, then `--header`, then the credentials file (`~/.prereason/credentials.json`, or `PREREASON_CREDENTIALS_FILE`, or `--credentials-file`), then the claim flow. The file holds the key and which claim issued it, never a claim token. On macOS and Linux the directory is created 0700 and the file 0600; Windows has no mode bits, so the file relies on your profile directory's permissions like every other credential store there.
|
|
211
|
+
|
|
212
|
+
## Claude Desktop extension (.mcpb)
|
|
213
|
+
|
|
214
|
+
`mcpb/manifest.json` describes the same bridge as a single click Claude Desktop extension, key optional. To build the bundle: `npm install --omit=dev`, then `npx @anthropic-ai/mcpb pack .` from the package directory, and install the resulting `.mcpb` by double clicking it. Submission to the Claude directory goes through the desktop extension form and is a publisher decision.
|
|
215
|
+
|
|
216
|
+
## No dependencies
|
|
217
|
+
|
|
218
|
+
The bridge ships its own transports and installs nothing. `npm ls` on it is one line, `npx @prereason/mcp` fetches one 23 KB tarball and starts, and the code a security review has to read is the code in this repository.
|
|
219
|
+
|
|
220
|
+
It used to depend on `@modelcontextprotocol/sdk` for two classes, a stdio transport and a Streamable HTTP client. That pulled in 91 packages and 25 MB on disk, nearly all of it the SDK server half: Express, Hono, CORS, a rate limiter, an OAuth client and a schema validator, none of which a relay ever calls. `lib/stdio.js` and `lib/streamable-http.js` replace the two classes the bridge used, keep their framing and their callbacks, and are covered by the suite under `test/`.
|
|
221
|
+
|
|
206
222
|
## Links
|
|
207
223
|
|
|
208
224
|
- [Documentation](https://www.prereason.com/docs#mcp)
|
package/bin/cli.js
CHANGED
|
@@ -1,82 +1,123 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* @prereason/mcp
|
|
3
|
+
* @prereason/mcp: the stdio bridge to PreReason's Streamable HTTP MCP endpoint.
|
|
4
4
|
*
|
|
5
|
-
* Connects
|
|
5
|
+
* Connects a stdio only client (Claude Desktop and others) to
|
|
6
|
+
* https://api.prereason.com/api/mcp.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* Where the key comes from, highest first:
|
|
9
|
+
* PREREASON_API_KEY in the environment
|
|
10
|
+
* --header "Authorization:Bearer pr_live_..."
|
|
11
|
+
* the credentials file (~/.prereason/credentials.json, or PREREASON_CREDENTIALS_FILE)
|
|
12
|
+
* no key: the bridge asks for access. It prints one link, the person opens it and
|
|
13
|
+
* approves, and the key arrives here and is saved. Free tools work meanwhile.
|
|
9
14
|
*
|
|
10
15
|
* Usage:
|
|
11
16
|
* npx @prereason/mcp
|
|
12
|
-
* npx @prereason/mcp
|
|
13
|
-
* npx @prereason/mcp
|
|
17
|
+
* npx @prereason/mcp --login request access now and save the key, then exit
|
|
18
|
+
* npx @prereason/mcp --logout delete the saved key, then exit
|
|
19
|
+
* npx @prereason/mcp [--header Key:Value]... [--credentials-file <path>] [<URL>]
|
|
14
20
|
*
|
|
15
|
-
* Environment
|
|
16
|
-
* PREREASON_API_KEY
|
|
17
|
-
* PREREASON_URL
|
|
21
|
+
* Environment:
|
|
22
|
+
* PREREASON_API_KEY Your API key (adds Authorization: Bearer)
|
|
23
|
+
* PREREASON_URL Override the endpoint URL
|
|
24
|
+
* PREREASON_CREDENTIALS_FILE Where a claimed key is kept
|
|
25
|
+
* PREREASON_CLIENT The app this bridge runs in (claude-desktop, cursor, ...),
|
|
26
|
+
* sent as X-PreReason-Client so your dashboard names it
|
|
18
27
|
*/
|
|
19
28
|
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
29
|
+
import { platform } from 'node:os';
|
|
30
|
+
import { StdioServerTransport } from '../lib/stdio.js';
|
|
31
|
+
import { StreamableHttpClientTransport } from '../lib/streamable-http.js';
|
|
32
|
+
import { credentialsPath, deleteCredentials, parseArgs, resolveApiKey } from '../lib/credentials.js';
|
|
33
|
+
import { decorateAuthRequired, runClaimFlow } from '../lib/claim.js';
|
|
22
34
|
|
|
23
35
|
// Keep in sync with package.json on each release
|
|
24
36
|
const PKG_NAME = '@prereason/mcp';
|
|
25
|
-
const PKG_VERSION = '0.
|
|
37
|
+
const PKG_VERSION = '0.5.0';
|
|
26
38
|
const DEFAULT_URL = 'https://api.prereason.com/api/mcp';
|
|
39
|
+
const USER_AGENT = `prereason-mcp/${PKG_VERSION} node/${process.versions.node} (${platform()})`;
|
|
27
40
|
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
const args = parseArgs(process.argv.slice(2));
|
|
42
|
+
|
|
43
|
+
if (args.help) {
|
|
30
44
|
process.stderr.write(`${PKG_NAME} v${PKG_VERSION}\n\n`);
|
|
31
45
|
process.stderr.write('Usage:\n');
|
|
32
|
-
process.stderr.write(' npx @prereason/mcp\n');
|
|
33
|
-
process.stderr.write(' npx @prereason/mcp
|
|
34
|
-
process.stderr.write('
|
|
35
|
-
process.stderr.write('
|
|
36
|
-
process.stderr.write('
|
|
46
|
+
process.stderr.write(' npx @prereason/mcp run the bridge (asks for access when no key is configured)\n');
|
|
47
|
+
process.stderr.write(' npx @prereason/mcp --login request access now, save the key, exit\n');
|
|
48
|
+
process.stderr.write(' npx @prereason/mcp --logout delete the saved key, exit\n');
|
|
49
|
+
process.stderr.write(' npx @prereason/mcp [--header Key:Value]... [--credentials-file <path>] [<URL>]\n\n');
|
|
50
|
+
process.stderr.write('Environment variables:\n');
|
|
51
|
+
process.stderr.write(' PREREASON_API_KEY Your API key (adds Authorization: Bearer header)\n');
|
|
52
|
+
process.stderr.write(' PREREASON_URL Override the default endpoint URL\n');
|
|
53
|
+
process.stderr.write(' PREREASON_CREDENTIALS_FILE Where a claimed key is kept (default ~/.prereason/credentials.json)\n');
|
|
54
|
+
process.stderr.write(' PREREASON_CLIENT The app this bridge runs in, e.g. claude-desktop\n\n');
|
|
37
55
|
process.stderr.write('Options:\n');
|
|
38
|
-
process.stderr.write(' --header Key:Value
|
|
39
|
-
process.stderr.write(' --
|
|
40
|
-
process.stderr.write(' --
|
|
56
|
+
process.stderr.write(' --header Key:Value Add an HTTP header (can be repeated)\n');
|
|
57
|
+
process.stderr.write(' --credentials-file <path> Use this credentials file\n');
|
|
58
|
+
process.stderr.write(' --login Ask for access now and save the key\n');
|
|
59
|
+
process.stderr.write(' --logout Forget the saved key\n');
|
|
60
|
+
process.stderr.write(' --help, -h Show this help\n');
|
|
61
|
+
process.stderr.write(' --version, -v Show version\n\n');
|
|
41
62
|
process.stderr.write(`Default URL: ${DEFAULT_URL}\n`);
|
|
42
63
|
process.exit(0);
|
|
43
64
|
}
|
|
44
65
|
|
|
45
|
-
if (
|
|
66
|
+
if (args.version) {
|
|
46
67
|
process.stderr.write(`${PKG_VERSION}\n`);
|
|
47
68
|
process.exit(0);
|
|
48
69
|
}
|
|
49
70
|
|
|
50
|
-
// ---
|
|
51
|
-
|
|
52
|
-
const
|
|
71
|
+
// --- Resolve the endpoint, the headers and the key ---
|
|
72
|
+
const url = new URL(args.url || process.env.PREREASON_URL || DEFAULT_URL);
|
|
73
|
+
const credentialsFile = credentialsPath({ env: process.env, flag: args.credentialsFile });
|
|
53
74
|
|
|
54
|
-
if (
|
|
55
|
-
|
|
75
|
+
if (args.logout) {
|
|
76
|
+
const existed = deleteCredentials(credentialsFile);
|
|
77
|
+
process.stderr.write(existed ? `PreReason: removed ${credentialsFile}\n` : `PreReason: nothing saved at ${credentialsFile}\n`);
|
|
78
|
+
process.exit(0);
|
|
56
79
|
}
|
|
57
80
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
/** Headers every request carries. The claim flow adds Authorization to this same object once a key arrives. */
|
|
82
|
+
const headers = { 'User-Agent': USER_AGENT, ...args.headers };
|
|
83
|
+
|
|
84
|
+
// Which app this bridge runs in, forwarded as X-PreReason-Client so the
|
|
85
|
+
// dashboard can name the connection. Only the documented ids are useful, and
|
|
86
|
+
// only a header safe value is ever sent.
|
|
87
|
+
const clientApp = /^[a-z0-9-]{1,32}$/.test(process.env.PREREASON_CLIENT || '') ? process.env.PREREASON_CLIENT : null;
|
|
88
|
+
if (clientApp) headers['X-PreReason-Client'] = clientApp;
|
|
89
|
+
|
|
90
|
+
const resolved = resolveApiKey({ env: process.env, headers: args.headers, credentialsFile });
|
|
91
|
+
if (resolved.key && !Object.keys(headers).some((h) => h.toLowerCase() === 'authorization')) {
|
|
92
|
+
headers.Authorization = `Bearer ${resolved.key}`;
|
|
69
93
|
}
|
|
70
94
|
|
|
71
|
-
const
|
|
95
|
+
const claimContext = {
|
|
96
|
+
mcpUrl: url.toString(),
|
|
97
|
+
headers,
|
|
98
|
+
clientName: `${PKG_NAME} ${PKG_VERSION} (${platform()})`,
|
|
99
|
+
purpose: clientApp ? `MCP bridge for ${clientApp}` : 'MCP bridge on this computer',
|
|
100
|
+
credentialsFile,
|
|
101
|
+
requestHeaders: clientApp ? { 'user-agent': USER_AGENT, 'x-prereason-client': clientApp } : { 'user-agent': USER_AGENT },
|
|
102
|
+
};
|
|
72
103
|
|
|
73
|
-
// ---
|
|
104
|
+
// --- --login: run the claim flow in the foreground and exit ---
|
|
105
|
+
if (args.login) {
|
|
106
|
+
if (resolved.key && resolved.source !== 'file') {
|
|
107
|
+
process.stderr.write(`PreReason: a key is already configured through ${resolved.source === 'env' ? 'PREREASON_API_KEY' : '--header'}; --login is for the credentials file. Nothing to do.\n`);
|
|
108
|
+
process.exit(0);
|
|
109
|
+
}
|
|
110
|
+
const outcome = await runClaimFlow(claimContext);
|
|
111
|
+
process.exit(outcome.outcome === 'approved' ? 0 : 1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// --- Transports: stdio to the host, Streamable HTTP to PreReason ---
|
|
74
115
|
const stdio = new StdioServerTransport();
|
|
75
|
-
const http = new
|
|
76
|
-
|
|
77
|
-
|
|
116
|
+
const http = new StreamableHttpClientTransport(url, { requestInit: { headers } });
|
|
117
|
+
|
|
118
|
+
/** While a claim is pending, the approve link the tool results carry. */
|
|
119
|
+
const pending = { approveUrl: null, claimCode: null };
|
|
78
120
|
|
|
79
|
-
// --- Wire message routing ---
|
|
80
121
|
stdio.onmessage = (msg) => {
|
|
81
122
|
http.send(msg).catch((e) => {
|
|
82
123
|
process.stderr.write(`[prereason:send] ${e.message}\n`);
|
|
@@ -84,16 +125,14 @@ stdio.onmessage = (msg) => {
|
|
|
84
125
|
};
|
|
85
126
|
|
|
86
127
|
http.onmessage = (msg) => {
|
|
87
|
-
stdio.send(msg).catch((e) => {
|
|
128
|
+
stdio.send(decorateAuthRequired(msg, pending.approveUrl)).catch((e) => {
|
|
88
129
|
process.stderr.write(`[prereason:recv] ${e.message}\n`);
|
|
89
130
|
});
|
|
90
131
|
};
|
|
91
132
|
|
|
92
|
-
// --- Error handling ---
|
|
93
133
|
stdio.onerror = (e) => process.stderr.write(`[prereason:stdio] ${e.message}\n`);
|
|
94
134
|
http.onerror = (e) => process.stderr.write(`[prereason:http] ${e.message}\n`);
|
|
95
135
|
|
|
96
|
-
// --- Graceful shutdown ---
|
|
97
136
|
stdio.onclose = () => {
|
|
98
137
|
http.close();
|
|
99
138
|
process.exit(0);
|
|
@@ -104,6 +143,15 @@ http.onclose = () => {
|
|
|
104
143
|
process.exit(0);
|
|
105
144
|
};
|
|
106
145
|
|
|
107
|
-
//
|
|
146
|
+
// Start serving first: the host must never see "server disconnected" because
|
|
147
|
+
// a claim is waiting on a person. The free tools work without a key.
|
|
108
148
|
await http.start();
|
|
109
149
|
await stdio.start();
|
|
150
|
+
|
|
151
|
+
if (!resolved.key) {
|
|
152
|
+
// Fire and forget: the flow logs its own lines and attaches the key to
|
|
153
|
+
// `headers` when the person approves, which the transport reads per request.
|
|
154
|
+
runClaimFlow({ ...claimContext, state: pending }).catch((e) => {
|
|
155
|
+
process.stderr.write(`[prereason:claim] ${e.message}\n`);
|
|
156
|
+
});
|
|
157
|
+
}
|