@orrery-labs/mcp 0.1.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Orrery
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # @orrery-labs/mcp
2
+
3
+ Local stdio MCP bridge for Orrery.
4
+
5
+ Use this package when an MCP client prefers launching a local server process
6
+ instead of connecting directly to Orrery's remote HTTP endpoint.
7
+
8
+ The bridge is intentionally thin:
9
+
10
+ ```text
11
+ Claude / Cursor / local agent
12
+ ↓ stdio MCP (Content-Length framed JSON-RPC)
13
+ @orrery-labs/mcp
14
+ ↓ HTTPS JSON-RPC
15
+ https://orrery.me/api/mcp/v1
16
+
17
+ Orrery x402/API-credit tools
18
+ ```
19
+
20
+ It does not duplicate Orrery taxonomy, pricing, Kalshi, alert, or signal
21
+ logic. All tool calls are forwarded to the canonical remote MCP endpoint.
22
+
23
+ ## Install
24
+
25
+ ```bash
26
+ npx @orrery-labs/mcp
27
+ ```
28
+
29
+ ## Claude Desktop
30
+
31
+ ```json
32
+ {
33
+ "mcpServers": {
34
+ "orrery": {
35
+ "command": "npx",
36
+ "args": ["-y", "@orrery-labs/mcp"],
37
+ "env": {
38
+ "ORRERY_API_KEY": "orrery_live_..."
39
+ }
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ## Cursor
46
+
47
+ ```json
48
+ {
49
+ "mcpServers": {
50
+ "orrery": {
51
+ "command": "npx",
52
+ "args": ["-y", "@orrery-labs/mcp"],
53
+ "env": {
54
+ "ORRERY_API_KEY": "orrery_live_..."
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ## Configuration
62
+
63
+ | Env var | Default | Purpose |
64
+ |---|---|---|
65
+ | `ORRERY_MCP_URL` | `https://orrery.me/api/mcp/v1` | Remote MCP endpoint |
66
+ | `ORRERY_API_KEY` | unset | Orrery API-credit key, forwarded as `X-Orrery-API-Key` |
67
+ | `ORRERY_X_PAYMENT` | unset | x402 payment proof, forwarded as `X-PAYMENT` |
68
+ | `ORRERY_PAYMENT_SIGNATURE` | unset | x402 payment proof, forwarded as `PAYMENT-SIGNATURE` |
69
+ | `ORRERY_MCP_STDIO_FRAMING` | `content-length` | Use `newline` only for local smoke tests |
70
+
71
+ CLI flags mirror the env vars:
72
+
73
+ ```bash
74
+ orrery-mcp --url https://orrery.me/api/mcp/v1 --api-key orrery_live_...
75
+ ```
76
+
77
+ For quick shell smoke tests that send one JSON-RPC object per line:
78
+
79
+ ```bash
80
+ ORRERY_MCP_STDIO_FRAMING=newline orrery-mcp --url http://localhost:3000/api/mcp/v1
81
+ ```
82
+
83
+ ## Safety
84
+
85
+ Orrery is read-only. The MCP bridge exposes intelligence tools, resources,
86
+ and prompts. It does not trade, submit orders, connect to Polymarket/Kalshi
87
+ accounts, read private positions, or provide buy/sell recommendations.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ import { stdin, stdout, stderr, exit } from "node:process";
3
+ function parseArgs(argv) {
4
+ const opts = {
5
+ url: process.env.ORRERY_MCP_URL ?? "https://orrery.me/api/mcp/v1",
6
+ apiKey: process.env.ORRERY_API_KEY ?? null,
7
+ payment: process.env.ORRERY_X_PAYMENT ?? null,
8
+ paymentSignature: process.env.ORRERY_PAYMENT_SIGNATURE ?? null,
9
+ framing: process.env.ORRERY_MCP_STDIO_FRAMING === "newline"
10
+ ? "newline"
11
+ : "content-length",
12
+ };
13
+ for (let i = 0; i < argv.length; i += 1) {
14
+ const arg = argv[i];
15
+ if (arg === "--help" || arg === "-h") {
16
+ printHelp();
17
+ exit(0);
18
+ }
19
+ if (arg === "--url")
20
+ opts.url = requireValue(argv, ++i, "--url");
21
+ else if (arg === "--api-key")
22
+ opts.apiKey = requireValue(argv, ++i, "--api-key");
23
+ else if (arg === "--payment")
24
+ opts.payment = requireValue(argv, ++i, "--payment");
25
+ else if (arg === "--payment-signature") {
26
+ opts.paymentSignature = requireValue(argv, ++i, "--payment-signature");
27
+ }
28
+ else if (arg === "--stdio-framing") {
29
+ const value = requireValue(argv, ++i, "--stdio-framing");
30
+ if (value !== "content-length" && value !== "newline") {
31
+ throw new Error("--stdio-framing must be content-length or newline");
32
+ }
33
+ opts.framing = value;
34
+ }
35
+ else {
36
+ throw new Error(`Unknown argument: ${arg}`);
37
+ }
38
+ }
39
+ opts.url = opts.url.replace(/\/$/, "");
40
+ return opts;
41
+ }
42
+ function requireValue(argv, index, flag) {
43
+ const value = argv[index];
44
+ if (!value || value.startsWith("--"))
45
+ throw new Error(`Missing value for ${flag}`);
46
+ return value;
47
+ }
48
+ function printHelp() {
49
+ stderr.write(`@orrery-labs/mcp
50
+
51
+ Local stdio MCP bridge for Orrery.
52
+
53
+ Usage:
54
+ orrery-mcp [--url URL] [--api-key KEY] [--payment PROOF]
55
+
56
+ Env:
57
+ ORRERY_MCP_URL default https://orrery.me/api/mcp/v1
58
+ ORRERY_API_KEY forwarded as X-Orrery-API-Key
59
+ ORRERY_X_PAYMENT forwarded as X-PAYMENT
60
+ ORRERY_PAYMENT_SIGNATURE forwarded as PAYMENT-SIGNATURE
61
+ ORRERY_MCP_STDIO_FRAMING content-length (default) or newline
62
+
63
+ The process accepts standard MCP Content-Length framed JSON-RPC from
64
+ stdin and writes Content-Length framed responses to stdout. It also
65
+ accepts newline-delimited JSON-RPC for smoke tests; pass
66
+ --stdio-framing newline if your test harness expects line output.
67
+ `);
68
+ }
69
+ async function main() {
70
+ let opts;
71
+ try {
72
+ opts = parseArgs(process.argv.slice(2));
73
+ }
74
+ catch (err) {
75
+ stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
76
+ printHelp();
77
+ exit(1);
78
+ }
79
+ let input = Buffer.alloc(0);
80
+ let processing = false;
81
+ async function pump() {
82
+ if (processing)
83
+ return;
84
+ processing = true;
85
+ try {
86
+ while (true) {
87
+ const message = readNextMessage();
88
+ if (message === null)
89
+ return;
90
+ try {
91
+ await handlePayloadText(message, opts);
92
+ }
93
+ catch (err) {
94
+ write({
95
+ jsonrpc: "2.0",
96
+ id: null,
97
+ error: {
98
+ code: -32603,
99
+ message: err instanceof Error ? err.message : String(err),
100
+ },
101
+ }, opts);
102
+ }
103
+ }
104
+ }
105
+ finally {
106
+ processing = false;
107
+ }
108
+ }
109
+ function readNextMessage() {
110
+ if (input.length === 0)
111
+ return null;
112
+ const headerEnd = input.indexOf("\r\n\r\n");
113
+ if (headerEnd >= 0) {
114
+ const header = input.subarray(0, headerEnd).toString("utf8");
115
+ const match = /^Content-Length:\s*(\d+)\s*$/im.exec(header);
116
+ if (match) {
117
+ const length = Number(match[1]);
118
+ if (!Number.isSafeInteger(length) || length < 0) {
119
+ input = input.subarray(headerEnd + 4);
120
+ write({
121
+ jsonrpc: "2.0",
122
+ id: null,
123
+ error: { code: -32700, message: "Invalid Content-Length header" },
124
+ }, opts);
125
+ return null;
126
+ }
127
+ const bodyStart = headerEnd + 4;
128
+ const bodyEnd = bodyStart + length;
129
+ if (input.length < bodyEnd)
130
+ return null;
131
+ const body = input.subarray(bodyStart, bodyEnd).toString("utf8");
132
+ input = input.subarray(bodyEnd);
133
+ return body;
134
+ }
135
+ }
136
+ const preview = input.subarray(0, Math.min(64, input.length)).toString("utf8");
137
+ if (/^Content-Length:/i.test(preview) && headerEnd < 0)
138
+ return null;
139
+ const newline = input.indexOf(10);
140
+ if (newline < 0)
141
+ return null;
142
+ const line = input.subarray(0, newline).toString("utf8").trim();
143
+ input = input.subarray(newline + 1);
144
+ return line || null;
145
+ }
146
+ stdin.on("data", (chunk) => {
147
+ input = Buffer.concat([input, chunk]);
148
+ void pump();
149
+ });
150
+ stdin.on("error", (err) => {
151
+ stderr.write(`stdin error: ${err.message}\n`);
152
+ exit(1);
153
+ });
154
+ }
155
+ async function handlePayloadText(text, opts) {
156
+ const trimmed = text.trim();
157
+ if (!trimmed)
158
+ return;
159
+ let payload;
160
+ try {
161
+ payload = JSON.parse(trimmed);
162
+ }
163
+ catch {
164
+ write({
165
+ jsonrpc: "2.0",
166
+ id: null,
167
+ error: { code: -32700, message: "Parse error: invalid JSON" },
168
+ }, opts);
169
+ return;
170
+ }
171
+ const response = await forward(payload, opts);
172
+ if (response !== null)
173
+ write(response, opts);
174
+ }
175
+ async function forward(payload, opts) {
176
+ const headers = {
177
+ "Content-Type": "application/json",
178
+ Accept: "application/json",
179
+ "X-Orrery-MCP-Bridge": "@orrery-labs/mcp/0.1.0",
180
+ };
181
+ if (opts.apiKey)
182
+ headers["X-Orrery-API-Key"] = opts.apiKey;
183
+ if (opts.payment)
184
+ headers["X-PAYMENT"] = opts.payment;
185
+ if (opts.paymentSignature)
186
+ headers["PAYMENT-SIGNATURE"] = opts.paymentSignature;
187
+ const res = await fetch(opts.url, {
188
+ method: "POST",
189
+ headers,
190
+ body: JSON.stringify(payload),
191
+ });
192
+ // MCP notifications do not require a response. Orrery returns 204 for
193
+ // notifications/initialized; keep stdout clean in that case.
194
+ if (res.status === 204)
195
+ return null;
196
+ const text = await res.text();
197
+ if (!text.trim())
198
+ return null;
199
+ try {
200
+ return JSON.parse(text);
201
+ }
202
+ catch {
203
+ return {
204
+ jsonrpc: "2.0",
205
+ id: idFor(payload),
206
+ error: {
207
+ code: -32603,
208
+ message: `Remote MCP returned non-JSON HTTP ${res.status}`,
209
+ data: text.slice(0, 500),
210
+ },
211
+ };
212
+ }
213
+ }
214
+ function idFor(payload) {
215
+ if (Array.isArray(payload))
216
+ return null;
217
+ return payload.id ?? null;
218
+ }
219
+ function write(value, opts) {
220
+ const body = JSON.stringify(value);
221
+ if (opts.framing === "newline") {
222
+ stdout.write(`${body}\n`);
223
+ return;
224
+ }
225
+ const length = Buffer.byteLength(body, "utf8");
226
+ stdout.write(`Content-Length: ${length}\r\n\r\n${body}`);
227
+ }
228
+ void main();
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@orrery-labs/mcp",
3
+ "version": "0.1.0",
4
+ "description": "Local stdio MCP bridge for Orrery prediction-market intelligence.",
5
+ "type": "module",
6
+ "bin": {
7
+ "orrery-mcp": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc -p tsconfig.json && chmod +x dist/index.js",
16
+ "check": "tsc -p tsconfig.json --noEmit",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "keywords": [
23
+ "orrery",
24
+ "mcp",
25
+ "model-context-protocol",
26
+ "prediction-markets",
27
+ "polymarket",
28
+ "kalshi",
29
+ "agents"
30
+ ],
31
+ "homepage": "https://orrery.me/docs/agents/mcp",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/bez111/orrery.git",
35
+ "directory": "sdk/mcp"
36
+ },
37
+ "license": "MIT",
38
+ "author": "Orrery <hello@orrery.me>",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.10.0",
44
+ "typescript": "^5.7.0"
45
+ }
46
+ }