otto-intel-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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +102 -0
  3. package/dist/adapter/cdp-signer.d.ts +133 -0
  4. package/dist/adapter/cdp-signer.js +356 -0
  5. package/dist/adapter/cdp-signer.js.map +1 -0
  6. package/dist/adapter/chain.d.ts +36 -0
  7. package/dist/adapter/chain.js +65 -0
  8. package/dist/adapter/chain.js.map +1 -0
  9. package/dist/adapter/erc20.d.ts +39 -0
  10. package/dist/adapter/erc20.js +17 -0
  11. package/dist/adapter/erc20.js.map +1 -0
  12. package/dist/adapter/lifi-decode.d.ts +52 -0
  13. package/dist/adapter/lifi-decode.js +149 -0
  14. package/dist/adapter/lifi-decode.js.map +1 -0
  15. package/dist/adapter/refusal.d.ts +21 -0
  16. package/dist/adapter/refusal.js +55 -0
  17. package/dist/adapter/refusal.js.map +1 -0
  18. package/dist/adapter/sent-step.d.ts +15 -0
  19. package/dist/adapter/sent-step.js +6 -0
  20. package/dist/adapter/sent-step.js.map +1 -0
  21. package/dist/adapter/verify.d.ts +149 -0
  22. package/dist/adapter/verify.js +432 -0
  23. package/dist/adapter/verify.js.map +1 -0
  24. package/dist/adapter-cdp-index.d.ts +6 -0
  25. package/dist/adapter-cdp-index.js +7 -0
  26. package/dist/adapter-index.d.ts +15 -0
  27. package/dist/adapter-index.js +15 -0
  28. package/dist/artifact-id.d.ts +16 -0
  29. package/dist/artifact-id.js +60 -0
  30. package/dist/boot-redaction.d.ts +16 -0
  31. package/dist/boot-redaction.js +54 -0
  32. package/dist/data-source.d.ts +7 -0
  33. package/dist/data-source.js +2 -0
  34. package/dist/errors.d.ts +70 -0
  35. package/dist/errors.js +221 -0
  36. package/dist/execution-config.d.ts +232 -0
  37. package/dist/execution-config.js +443 -0
  38. package/dist/execution-delegated-definition.d.ts +165 -0
  39. package/dist/execution-delegated-definition.js +116 -0
  40. package/dist/execution-delegation-admin-definition.d.ts +208 -0
  41. package/dist/execution-delegation-admin-definition.js +170 -0
  42. package/dist/execution-delegation-admin.d.ts +74 -0
  43. package/dist/execution-delegation-admin.js +290 -0
  44. package/dist/execution-delegation-policy.d.ts +257 -0
  45. package/dist/execution-delegation-policy.js +279 -0
  46. package/dist/execution-delegation.d.ts +190 -0
  47. package/dist/execution-delegation.js +545 -0
  48. package/dist/execution-errors.d.ts +9 -0
  49. package/dist/execution-errors.js +128 -0
  50. package/dist/execution-index.d.ts +17 -0
  51. package/dist/execution-index.js +16 -0
  52. package/dist/execution-intent.d.ts +14 -0
  53. package/dist/execution-intent.js +36 -0
  54. package/dist/execution-registration.d.ts +113 -0
  55. package/dist/execution-registration.js +161 -0
  56. package/dist/execution-tool-definitions.d.ts +1103 -0
  57. package/dist/execution-tool-definitions.js +1051 -0
  58. package/dist/execution-tools.d.ts +85 -0
  59. package/dist/execution-tools.js +690 -0
  60. package/dist/execution-types.d.ts +274 -0
  61. package/dist/execution-types.js +157 -0
  62. package/dist/hyperliquid-info-client.d.ts +31 -0
  63. package/dist/hyperliquid-info-client.js +121 -0
  64. package/dist/hyperliquid-order-assertions.d.ts +89 -0
  65. package/dist/hyperliquid-order-assertions.js +325 -0
  66. package/dist/hyperliquid-order-builder.d.ts +47 -0
  67. package/dist/hyperliquid-order-builder.js +55 -0
  68. package/dist/lifi-bridge-assertions.d.ts +49 -0
  69. package/dist/lifi-bridge-assertions.js +303 -0
  70. package/dist/lifi-execution-client.d.ts +74 -0
  71. package/dist/lifi-execution-client.js +165 -0
  72. package/dist/lifi-fee-assertions.d.ts +32 -0
  73. package/dist/lifi-fee-assertions.js +103 -0
  74. package/dist/lifi-swap-assertions.d.ts +32 -0
  75. package/dist/lifi-swap-assertions.js +214 -0
  76. package/dist/local-config.d.ts +9 -0
  77. package/dist/local-config.js +45 -0
  78. package/dist/mcp-server.d.ts +4 -0
  79. package/dist/mcp-server.js +44 -0
  80. package/dist/polymarket-clob-client.d.ts +73 -0
  81. package/dist/polymarket-clob-client.js +186 -0
  82. package/dist/polymarket-order-assertions.d.ts +60 -0
  83. package/dist/polymarket-order-assertions.js +273 -0
  84. package/dist/polymarket-order-builder.d.ts +43 -0
  85. package/dist/polymarket-order-builder.js +74 -0
  86. package/dist/prepared-artifacts.d.ts +45 -0
  87. package/dist/prepared-artifacts.js +92 -0
  88. package/dist/stdio-main.d.ts +2 -0
  89. package/dist/stdio-main.js +20 -0
  90. package/dist/tool-definitions.d.ts +20 -0
  91. package/dist/tool-definitions.js +162 -0
  92. package/dist/x402-read-source.d.ts +11 -0
  93. package/dist/x402-read-source.js +77 -0
  94. package/package.json +162 -0
@@ -0,0 +1,45 @@
1
+ /**
2
+ * prepared-artifacts.ts — the artifacts THIS process prepared, for the delegated-send path.
3
+ *
4
+ * Otto is the submitter on `otto_submit_under_delegation`, so the per-artifact idempotency record (sent →
5
+ * replayed, halted → never retried) must be authoritative for every artifact the tool accepts. That
6
+ * record is process-local, and a rolling replacement runs two processes at once: an artifact prepared and
7
+ * SENT through process A after process B booted has no record on B, and B would send it again. A
8
+ * boot-time fence ("refuse artifacts older than this process") does not cover that overlap.
9
+ *
10
+ * The rule that does: the delegated tool accepts ONLY artifacts this process prepared — by id AND by the
11
+ * envelope's whole-envelope commitment (`submission.artifact_digest`), so a re-IDed envelope prepared
12
+ * elsewhere and recommitted under an id this process minted is refused too. Every hosted
13
+ * `otto_prepare_swap` records its artifact here (when delegation is configured); the delegated tool refuses
14
+ * anything it does not find (`ARTIFACT_NOT_PREPARED_HERE`) before reading anything. "The submission record
15
+ * exists on the process that submits" then holds by construction: prepare and submit land on the same
16
+ * process, or the submit is refused and the caller prepares again — rare (only when prepare → submit spans
17
+ * a deploy), fail-closed by design, no database, no cross-process state.
18
+ *
19
+ * LIVE ENTRIES ARE NEVER EVICTED. An entry lives for the artifact's freshness window plus the submission
20
+ * grace — exactly as long as its submission record can — and is removed only by that expiry. The registry
21
+ * is bounded by refusing a NEW prepare at capacity (`PREPARED_ARTIFACT_CAPACITY`), never by dropping an
22
+ * artifact already issued: an issued artifact that was SENT or HALTED must always find its record and
23
+ * replay, which it cannot if its prepared-here entry vanished first. Entries are inserted in prepare order
24
+ * with a constant TTL, so insertion order is expiry order and the sweep stops at the first live entry.
25
+ */
26
+ /** How long after `valid_until` a submission record — and the prepared-here entry it needs — is kept. */
27
+ export declare const DELEGATED_SUBMISSION_GRACE_MS: number;
28
+ /** Hard bound on LIVE entries per registry; at capacity a new prepare is refused (nothing issued is dropped). */
29
+ export declare const PREPARED_ARTIFACTS_MAX_ENTRIES = 100000;
30
+ /**
31
+ * Record an artifact this process just prepared; it stays submittable here until `validUntilMs` + grace.
32
+ * An id is issued once: the same id with the same commitment is a no-op (the first record stands, which
33
+ * keeps the append-only expiry order); the same id with DIFFERENT content is an entropy collision and the
34
+ * prepare is refused rather than issuing an artifact this registry cannot vouch for. Refuses at capacity —
35
+ * the caller's prepare fails, and nothing already issued is touched.
36
+ */
37
+ export declare function recordPreparedArtifact(owner: object, artifactId: string, commitment: string, validUntilMs: number, nowMs: number): void;
38
+ /**
39
+ * True only for an artifact THIS process prepared — same id, same commitment — whose window (+ grace) has
40
+ * not passed. The expiry is checked here as well as by the sweep, so an entry a backwards clock left
41
+ * behind a live one never answers past its window.
42
+ */
43
+ export declare function preparedHere(owner: object, artifactId: string, commitment: string, nowMs: number): boolean;
44
+ /** How many live entries the registry holds (tests). */
45
+ export declare function preparedArtifactCount(owner: object, nowMs: number): number;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * prepared-artifacts.ts — the artifacts THIS process prepared, for the delegated-send path.
3
+ *
4
+ * Otto is the submitter on `otto_submit_under_delegation`, so the per-artifact idempotency record (sent →
5
+ * replayed, halted → never retried) must be authoritative for every artifact the tool accepts. That
6
+ * record is process-local, and a rolling replacement runs two processes at once: an artifact prepared and
7
+ * SENT through process A after process B booted has no record on B, and B would send it again. A
8
+ * boot-time fence ("refuse artifacts older than this process") does not cover that overlap.
9
+ *
10
+ * The rule that does: the delegated tool accepts ONLY artifacts this process prepared — by id AND by the
11
+ * envelope's whole-envelope commitment (`submission.artifact_digest`), so a re-IDed envelope prepared
12
+ * elsewhere and recommitted under an id this process minted is refused too. Every hosted
13
+ * `otto_prepare_swap` records its artifact here (when delegation is configured); the delegated tool refuses
14
+ * anything it does not find (`ARTIFACT_NOT_PREPARED_HERE`) before reading anything. "The submission record
15
+ * exists on the process that submits" then holds by construction: prepare and submit land on the same
16
+ * process, or the submit is refused and the caller prepares again — rare (only when prepare → submit spans
17
+ * a deploy), fail-closed by design, no database, no cross-process state.
18
+ *
19
+ * LIVE ENTRIES ARE NEVER EVICTED. An entry lives for the artifact's freshness window plus the submission
20
+ * grace — exactly as long as its submission record can — and is removed only by that expiry. The registry
21
+ * is bounded by refusing a NEW prepare at capacity (`PREPARED_ARTIFACT_CAPACITY`), never by dropping an
22
+ * artifact already issued: an issued artifact that was SENT or HALTED must always find its record and
23
+ * replay, which it cannot if its prepared-here entry vanished first. Entries are inserted in prepare order
24
+ * with a constant TTL, so insertion order is expiry order and the sweep stops at the first live entry.
25
+ */
26
+ import { refuse } from './execution-errors.js';
27
+ /** How long after `valid_until` a submission record — and the prepared-here entry it needs — is kept. */
28
+ export const DELEGATED_SUBMISSION_GRACE_MS = 10 * 60_000;
29
+ /** Hard bound on LIVE entries per registry; at capacity a new prepare is refused (nothing issued is dropped). */
30
+ export const PREPARED_ARTIFACTS_MAX_ENTRIES = 100_000;
31
+ /** One registry per owner (the delegation services instance); keyed by identity, so a test world is its own process. */
32
+ const REGISTRIES = new WeakMap();
33
+ function registryOf(owner, nowMs) {
34
+ let entries = REGISTRIES.get(owner);
35
+ if (!entries) {
36
+ entries = new Map();
37
+ REGISTRIES.set(owner, entries);
38
+ }
39
+ // Insertion order is expiry order (constant TTL from a monotonic clock): sweep from the front, stop at
40
+ // the first live entry — O(expired), not O(live), per call.
41
+ for (const [id, entry] of entries) {
42
+ if (entry.expiresAtMs > nowMs)
43
+ break;
44
+ entries.delete(id);
45
+ }
46
+ return entries;
47
+ }
48
+ /**
49
+ * Record an artifact this process just prepared; it stays submittable here until `validUntilMs` + grace.
50
+ * An id is issued once: the same id with the same commitment is a no-op (the first record stands, which
51
+ * keeps the append-only expiry order); the same id with DIFFERENT content is an entropy collision and the
52
+ * prepare is refused rather than issuing an artifact this registry cannot vouch for. Refuses at capacity —
53
+ * the caller's prepare fails, and nothing already issued is touched.
54
+ */
55
+ export function recordPreparedArtifact(owner, artifactId, commitment, validUntilMs, nowMs) {
56
+ const entries = registryOf(owner, nowMs);
57
+ const existing = entries.get(artifactId);
58
+ if (existing) {
59
+ if (existing.commitment === commitment)
60
+ return; // the same artifact again: nothing to change
61
+ // An id collision with DIFFERENT content: never issue an artifact this registry cannot vouch for.
62
+ return refuse('ARTIFACT_ID_COLLISION', 'the prepare minted an artifact id this server already holds for different content; prepare again');
63
+ }
64
+ if (entries.size >= PREPARED_ARTIFACTS_MAX_ENTRIES) {
65
+ // The rare path: a clock that stepped backwards can leave an expired entry behind a live one, past
66
+ // the front sweep. Sweep everything once before refusing, so capacity counts LIVE entries only.
67
+ for (const [id, entry] of entries)
68
+ if (entry.expiresAtMs <= nowMs)
69
+ entries.delete(id);
70
+ if (entries.size >= PREPARED_ARTIFACTS_MAX_ENTRIES) {
71
+ return refuse('PREPARED_ARTIFACT_CAPACITY', `this server holds its maximum of ${PREPARED_ARTIFACTS_MAX_ENTRIES} live prepared artifacts; retry after some expire`);
72
+ }
73
+ }
74
+ entries.set(artifactId, {
75
+ expiresAtMs: validUntilMs + DELEGATED_SUBMISSION_GRACE_MS,
76
+ commitment,
77
+ });
78
+ }
79
+ /**
80
+ * True only for an artifact THIS process prepared — same id, same commitment — whose window (+ grace) has
81
+ * not passed. The expiry is checked here as well as by the sweep, so an entry a backwards clock left
82
+ * behind a live one never answers past its window.
83
+ */
84
+ export function preparedHere(owner, artifactId, commitment, nowMs) {
85
+ const entry = registryOf(owner, nowMs).get(artifactId);
86
+ return entry !== undefined && entry.expiresAtMs > nowMs && entry.commitment === commitment;
87
+ }
88
+ /** How many live entries the registry holds (tests). */
89
+ export function preparedArtifactCount(owner, nowMs) {
90
+ return registryOf(owner, nowMs).size;
91
+ }
92
+ //# sourceMappingURL=prepared-artifacts.js.map
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { bootFailureLine } from './boot-redaction.js';
5
+ import { loadLocalX402Config } from './local-config.js';
6
+ import { createIntelMcpServer } from './mcp-server.js';
7
+ import { createExecutionToolRuntimeFromEnv } from './execution-tools.js';
8
+ import { X402ReadSource } from './x402-read-source.js';
9
+ async function main() {
10
+ const source = new X402ReadSource(loadLocalX402Config());
11
+ const server = createIntelMcpServer(source, createExecutionToolRuntimeFromEnv());
12
+ await server.connect(new StdioServerTransport());
13
+ }
14
+ main().catch((error) => {
15
+ // Boot errors here can carry X402_RPC_URL / X402_BASE_URL (viem embeds the transport URL) —
16
+ // the shared redacting formatter is the only allowed boot printer (c2 fold 08-30).
17
+ process.stderr.write(bootFailureLine('otto Intel MCP failed', error));
18
+ process.exitCode = 1;
19
+ });
20
+ //# sourceMappingURL=stdio-main.js.map
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export type IntelToolName = `otto_${string}`;
3
+ export type ToolArguments = Record<string, unknown>;
4
+ export interface IntelToolDefinition {
5
+ readonly name: IntelToolName;
6
+ readonly title: string;
7
+ readonly description: string;
8
+ readonly endpointId: string;
9
+ readonly publicPath: `/${string}`;
10
+ readonly inputSchema: z.ZodObject;
11
+ readonly priceUsd: string;
12
+ readonly maxPaymentAtomic: bigint;
13
+ readonly toQuery: (input: ToolArguments) => Record<string, string>;
14
+ }
15
+ /**
16
+ * The single v1 catalog consumed by both the hosted and stdio transports.
17
+ * Every entry is quote/data-only and backed by the data-endpoints service.
18
+ */
19
+ export declare const TOOL_DEFINITIONS: readonly IntelToolDefinition[];
20
+ export declare const TOOL_DEFINITION_BY_NAME: Map<`otto_${string}`, IntelToolDefinition>;
@@ -0,0 +1,162 @@
1
+ import { z } from 'zod';
2
+ const noArgumentsSchema = z.object({}).strict();
3
+ const thesisSchema = z
4
+ .object({
5
+ thesis: z
6
+ .string()
7
+ .trim()
8
+ .min(1, 'thesis must not be blank')
9
+ .max(200, 'thesis must be at most 200 characters')
10
+ .optional()
11
+ .describe('Natural-language screening thesis, for example "AI infrastructure chips".'),
12
+ })
13
+ .strict();
14
+ const tickerSchema = z
15
+ .object({
16
+ ticker: z
17
+ .string()
18
+ .trim()
19
+ .min(1)
20
+ .max(11)
21
+ .regex(/^\$?[A-Za-z][A-Za-z.-]{0,9}$/, 'ticker must be a US stock symbol')
22
+ .describe('US-listed stock ticker, for example NVDA, AAPL, or JPM.'),
23
+ })
24
+ .strict();
25
+ const managerSchema = z
26
+ .object({
27
+ manager: z
28
+ .string()
29
+ .trim()
30
+ .min(1)
31
+ .max(11)
32
+ .regex(/^\$?[A-Za-z0-9][A-Za-z0-9.-]{0,9}$/, 'manager must be an SEC CIK or manager ticker')
33
+ .describe('SEC manager CIK, such as 1067983, or listed manager ticker, such as BLK.'),
34
+ })
35
+ .strict();
36
+ function noQuery() {
37
+ return {};
38
+ }
39
+ function tickerQuery(input) {
40
+ return { ticker: String(input.ticker).replace(/^\$/, '').toUpperCase() };
41
+ }
42
+ function managerQuery(input) {
43
+ return { manager: String(input.manager).replace(/^\$/, '') };
44
+ }
45
+ function thesisQuery(input) {
46
+ return typeof input.thesis === 'string' ? { thesis: input.thesis } : {};
47
+ }
48
+ function usdcAtomic(priceUsd) {
49
+ if (!/^\d+\.\d{1,6}$/.test(priceUsd)) {
50
+ throw new Error(`invalid USDC price: ${priceUsd}`);
51
+ }
52
+ const [whole, fraction] = priceUsd.split('.');
53
+ return BigInt(whole) * 1000000n + BigInt(fraction.padEnd(6, '0'));
54
+ }
55
+ function defineTool(definition) {
56
+ return Object.freeze({
57
+ ...definition,
58
+ publicPath: definition.publicPath ?? `/${definition.endpointId}`,
59
+ maxPaymentAtomic: usdcAtomic(definition.priceUsd),
60
+ });
61
+ }
62
+ /**
63
+ * The single v1 catalog consumed by both the hosted and stdio transports.
64
+ * Every entry is quote/data-only and backed by the data-endpoints service.
65
+ */
66
+ export const TOOL_DEFINITIONS = Object.freeze([
67
+ defineTool({
68
+ name: 'otto_tokenized_equities',
69
+ title: 'Otto tokenized equities',
70
+ description: 'Scan the live registry of US equities and ETFs tokenized on Robinhood Chain (4663): symbol, canonical token address, decimals, reference price, and verifiable tradability signal. An optional thesis returns a relevance-ranked shortlist with a match reason per row. Data only; not a recommendation or executable quote.',
71
+ endpointId: 'tokenized-equities',
72
+ inputSchema: thesisSchema,
73
+ priceUsd: '0.001',
74
+ toQuery: thesisQuery,
75
+ }),
76
+ defineTool({
77
+ name: 'otto_rh_season',
78
+ title: 'Otto Robinhood Chain season',
79
+ description: 'Scan rug-screened Robinhood Chain (4663) movers with honeypot filters and per-token risk flags. Market context only; not a pick, recommendation, or trade instruction.',
80
+ endpointId: 'rh-season',
81
+ inputSchema: noArgumentsSchema,
82
+ priceUsd: '0.001',
83
+ toQuery: noQuery,
84
+ }),
85
+ defineTool({
86
+ name: 'otto_equity_intel',
87
+ title: 'Otto equity intelligence',
88
+ description: 'Read SEC-EDGAR fundamentals and filings context for a US ticker: revenue and net-income trends, margins, EPS, leverage, recent 10-K/10-Q/8-K filings, and a guarded AI summary. Fundamentals scanner only; not a price feed or recommendation.',
89
+ endpointId: 'equity-intel',
90
+ inputSchema: tickerSchema,
91
+ priceUsd: '0.003',
92
+ toQuery: tickerQuery,
93
+ }),
94
+ defineTool({
95
+ name: 'otto_insider_trades',
96
+ title: 'Otto insider trades',
97
+ description: 'Read parsed SEC Form 4 insider transactions for a US ticker, including purchase/sale counts, dollar values, roles, and net P/S balance. Filing scanner only; not a recommendation.',
98
+ endpointId: 'insider-trades',
99
+ inputSchema: tickerSchema,
100
+ priceUsd: '0.003',
101
+ toQuery: tickerQuery,
102
+ }),
103
+ defineTool({
104
+ name: 'otto_institutional_holdings',
105
+ title: 'Otto institutional holdings',
106
+ description: 'Read the latest SEC 13F-HR top positions for an institutional manager by CIK or manager ticker, with amendments applied. Ownership data only; not a recommendation.',
107
+ endpointId: 'institutional-holdings',
108
+ inputSchema: managerSchema,
109
+ priceUsd: '0.003',
110
+ toQuery: managerQuery,
111
+ }),
112
+ defineTool({
113
+ name: 'otto_equity_smart_money',
114
+ title: 'Otto equity smart-money scanner',
115
+ description: 'Read a public-domain SEC bundle for a US ticker: Form 4 insider activity plus trailing-twelve-month XBRL fundamentals. Scanner context only; not a pick or recommendation.',
116
+ endpointId: 'equity-smart-money',
117
+ inputSchema: tickerSchema,
118
+ priceUsd: '0.006',
119
+ toQuery: tickerQuery,
120
+ }),
121
+ defineTool({
122
+ name: 'otto_equity_smart_money_brief',
123
+ title: 'Otto equity smart-money brief',
124
+ description: 'Read one guarded AI brief over observed SEC Form 4 flow, 13D/13G ownership events, and fundamentals for a US ticker. Filing scanner only; generation rejects buy/sell advice and the result is not a recommendation.',
125
+ endpointId: 'equity-smart-money-brief',
126
+ inputSchema: tickerSchema,
127
+ priceUsd: '0.10',
128
+ toQuery: tickerQuery,
129
+ }),
130
+ defineTool({
131
+ name: 'otto_base_season',
132
+ title: 'Otto Base season',
133
+ description: 'Scan quality-screened Base tokens with current social-intelligence and trusted-KOL context. Market context only; not a pick, recommendation, or trade instruction.',
134
+ endpointId: 'base-season',
135
+ inputSchema: noArgumentsSchema,
136
+ // Phase-3 floor experiment: /base-season serves $0.002. The stdio selector treats this as a maximum
137
+ // (x402-read-source.ts) but the HOSTED source Railway runs requires amount === this
138
+ // (hosted-payment-source.ts), so it must ship WITH the price; guard: scripts/audit/client-price-caps.mjs.
139
+ priceUsd: '0.002',
140
+ toQuery: noQuery,
141
+ }),
142
+ defineTool({
143
+ name: 'otto_pm_markets',
144
+ title: 'Otto prediction-market context',
145
+ description: 'Read the highest-volume open Polymarket markets with live outcome probabilities, bid/ask context, liquidity, volume, and end dates. Event-probability context only; not a recommendation.',
146
+ endpointId: 'pm-markets',
147
+ inputSchema: noArgumentsSchema,
148
+ priceUsd: '0.001',
149
+ toQuery: noQuery,
150
+ }),
151
+ defineTool({
152
+ name: 'otto_pm_crypto',
153
+ title: 'Otto crypto prediction-market context',
154
+ description: 'Read high-volume open BTC, ETH, and crypto Polymarket markets with current outcome probabilities and market context. Event-probability data only; not a recommendation.',
155
+ endpointId: 'pm-crypto',
156
+ inputSchema: noArgumentsSchema,
157
+ priceUsd: '0.001',
158
+ toQuery: noQuery,
159
+ }),
160
+ ]);
161
+ export const TOOL_DEFINITION_BY_NAME = new Map(TOOL_DEFINITIONS.map((definition) => [definition.name, definition]));
162
+ //# sourceMappingURL=tool-definitions.js.map
@@ -0,0 +1,11 @@
1
+ import type { SelectPaymentRequirements } from '@x402/axios';
2
+ import type { LocalX402Config } from './local-config.js';
3
+ import type { IntelDataSource } from './data-source.js';
4
+ import type { IntelToolDefinition, ToolArguments } from './tool-definitions.js';
5
+ export declare function boundedPaymentSelector(maxPaymentAtomic: bigint): SelectPaymentRequirements;
6
+ export declare class X402ReadSource implements IntelDataSource {
7
+ private readonly config;
8
+ private readonly scheme;
9
+ constructor(config: LocalX402Config);
10
+ read(definition: IntelToolDefinition, input: ToolArguments): Promise<unknown>;
11
+ }
@@ -0,0 +1,77 @@
1
+ import axios from 'axios';
2
+ import { wrapAxiosWithPayment, x402Client } from '@x402/axios';
3
+ import { ExactEvmScheme } from '@x402/evm/exact/client';
4
+ import { toClientEvmSigner } from '@x402/evm';
5
+ import { createPublicClient, http } from 'viem';
6
+ import { base } from 'viem/chains';
7
+ import { privateKeyToAccount } from 'viem/accounts';
8
+ import { WarmReadUnavailableError, X402PaymentError } from './errors.js';
9
+ const BASE_NETWORK = 'eip155:8453';
10
+ const BASE_USDC = '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913';
11
+ function amountOf(requirement) {
12
+ if (!/^\d+$/.test(requirement.amount))
13
+ return null;
14
+ try {
15
+ return BigInt(requirement.amount);
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ export function boundedPaymentSelector(maxPaymentAtomic) {
22
+ return (_version, requirements) => {
23
+ const eligible = requirements
24
+ .map((requirement) => ({ requirement, amount: amountOf(requirement) }))
25
+ .filter((candidate) => candidate.amount !== null &&
26
+ candidate.amount > 0n &&
27
+ candidate.amount <= maxPaymentAtomic &&
28
+ candidate.requirement.scheme === 'exact' &&
29
+ candidate.requirement.network === BASE_NETWORK &&
30
+ candidate.requirement.asset.toLowerCase() === BASE_USDC &&
31
+ /^0x[a-fA-F0-9]{40}$/.test(candidate.requirement.payTo))
32
+ .sort((left, right) => left.amount < right.amount ? -1 : left.amount > right.amount ? 1 : 0);
33
+ const selected = eligible[0];
34
+ if (!selected)
35
+ throw new Error('No bounded Base USDC payment requirement is available');
36
+ return selected.requirement;
37
+ };
38
+ }
39
+ export class X402ReadSource {
40
+ config;
41
+ scheme;
42
+ constructor(config) {
43
+ this.config = config;
44
+ const account = privateKeyToAccount(config.privateKey);
45
+ const publicClient = createPublicClient({
46
+ chain: base,
47
+ transport: http(config.rpcUrl),
48
+ });
49
+ const signer = toClientEvmSigner(account, publicClient);
50
+ this.scheme = new ExactEvmScheme(signer);
51
+ }
52
+ async read(definition, input) {
53
+ const paymentClient = new x402Client(boundedPaymentSelector(definition.maxPaymentAtomic));
54
+ paymentClient.register('eip155:*', this.scheme);
55
+ const httpClient = wrapAxiosWithPayment(axios.create({
56
+ baseURL: this.config.baseUrl,
57
+ timeout: this.config.timeoutMs,
58
+ maxRedirects: 0,
59
+ headers: { Accept: 'application/json' },
60
+ }), paymentClient);
61
+ try {
62
+ const response = await httpClient.get(definition.publicPath, {
63
+ params: definition.toQuery(input),
64
+ });
65
+ return response.data;
66
+ }
67
+ catch (error) {
68
+ if (axios.isAxiosError(error) && error.response?.status === 503) {
69
+ const rawRetry = error.response.headers?.['retry-after'];
70
+ const parsedRetry = Number(rawRetry);
71
+ throw new WarmReadUnavailableError(Number.isFinite(parsedRetry) && parsedRetry > 0 ? Math.ceil(parsedRetry) : undefined);
72
+ }
73
+ throw new X402PaymentError();
74
+ }
75
+ }
76
+ }
77
+ //# sourceMappingURL=x402-read-source.js.map
package/package.json ADDED
@@ -0,0 +1,162 @@
1
+ {
2
+ "name": "otto-intel-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Read-only otto market intelligence MCP server for hosted and local x402 use",
5
+ "type": "module",
6
+ "bin": {
7
+ "otto-intel-mcp": "./dist/stdio-main.js"
8
+ },
9
+ "files": [
10
+ "dist/artifact-id.js",
11
+ "dist/data-source.js",
12
+ "dist/errors.js",
13
+ "dist/execution-config.js",
14
+ "dist/execution-errors.js",
15
+ "dist/execution-registration.js",
16
+ "dist/execution-tool-definitions.js",
17
+ "dist/execution-tools.js",
18
+ "dist/execution-types.js",
19
+ "dist/hyperliquid-info-client.js",
20
+ "dist/hyperliquid-order-assertions.js",
21
+ "dist/hyperliquid-order-builder.js",
22
+ "dist/lifi-bridge-assertions.js",
23
+ "dist/lifi-execution-client.js",
24
+ "dist/lifi-fee-assertions.js",
25
+ "dist/lifi-swap-assertions.js",
26
+ "dist/polymarket-clob-client.js",
27
+ "dist/polymarket-order-assertions.js",
28
+ "dist/polymarket-order-builder.js",
29
+ "dist/local-config.js",
30
+ "dist/mcp-server.js",
31
+ "dist/stdio-main.js",
32
+ "dist/tool-definitions.js",
33
+ "dist/x402-read-source.js",
34
+ "README.md",
35
+ "LICENSE",
36
+ "dist/execution-index.js",
37
+ "dist/artifact-id.d.ts",
38
+ "dist/data-source.d.ts",
39
+ "dist/errors.d.ts",
40
+ "dist/execution-config.d.ts",
41
+ "dist/execution-errors.d.ts",
42
+ "dist/execution-registration.d.ts",
43
+ "dist/execution-tool-definitions.d.ts",
44
+ "dist/execution-tools.d.ts",
45
+ "dist/execution-types.d.ts",
46
+ "dist/hyperliquid-info-client.d.ts",
47
+ "dist/hyperliquid-order-assertions.d.ts",
48
+ "dist/hyperliquid-order-builder.d.ts",
49
+ "dist/lifi-bridge-assertions.d.ts",
50
+ "dist/lifi-execution-client.d.ts",
51
+ "dist/lifi-fee-assertions.d.ts",
52
+ "dist/lifi-swap-assertions.d.ts",
53
+ "dist/polymarket-clob-client.d.ts",
54
+ "dist/polymarket-order-assertions.d.ts",
55
+ "dist/polymarket-order-builder.d.ts",
56
+ "dist/local-config.d.ts",
57
+ "dist/mcp-server.d.ts",
58
+ "dist/stdio-main.d.ts",
59
+ "dist/tool-definitions.d.ts",
60
+ "dist/x402-read-source.d.ts",
61
+ "dist/execution-index.d.ts",
62
+ "dist/execution-intent.js",
63
+ "dist/execution-intent.d.ts",
64
+ "dist/adapter",
65
+ "dist/adapter-index.js",
66
+ "dist/adapter-index.d.ts",
67
+ "dist/adapter-cdp-index.js",
68
+ "dist/adapter-cdp-index.d.ts",
69
+ "dist/execution-delegation.js",
70
+ "dist/execution-delegation.d.ts",
71
+ "dist/execution-delegation-policy.js",
72
+ "dist/execution-delegation-policy.d.ts",
73
+ "dist/execution-delegated-definition.js",
74
+ "dist/execution-delegated-definition.d.ts",
75
+ "dist/prepared-artifacts.js",
76
+ "dist/prepared-artifacts.d.ts",
77
+ "dist/execution-delegation-admin.js",
78
+ "dist/execution-delegation-admin.d.ts",
79
+ "dist/execution-delegation-admin-definition.js",
80
+ "dist/execution-delegation-admin-definition.d.ts",
81
+ "dist/boot-redaction.js",
82
+ "dist/boot-redaction.d.ts"
83
+ ],
84
+ "scripts": {
85
+ "build": "tsc -p tsconfig.build.json",
86
+ "start": "node dist/hosted-main.js",
87
+ "start:stdio": "node dist/stdio-main.js",
88
+ "dev:hosted": "tsx watch src/hosted-main.ts",
89
+ "dev:stdio": "tsx src/stdio-main.ts",
90
+ "demo:hosted": "tsx scripts/demo-hosted.ts",
91
+ "typecheck": "tsc --noEmit",
92
+ "test": "node --import tsx --test \"test/**/*.test.ts\"",
93
+ "test:liveness": "node --import tsx --test --test-name-pattern=liveness test/execution-tools.test.ts test/execution-bridge.test.ts test/execution-polymarket.test.ts test/execution-hyperliquid.test.ts",
94
+ "test:mutation": "node --import tsx test/lifi-fee-mutation-proof.ts && node --import tsx test/lifi-bridge-mutation-proof.ts && node --import tsx test/polymarket-order-mutation-proof.ts && node --import tsx test/hyperliquid-order-mutation-proof.ts",
95
+ "verify": "npm run typecheck && npm test && npm run build",
96
+ "prepublishOnly": "npm run verify"
97
+ },
98
+ "engines": {
99
+ "node": "=22.16.0",
100
+ "npm": ">=10.0.0"
101
+ },
102
+ "keywords": [
103
+ "mcp",
104
+ "x402",
105
+ "market-intelligence",
106
+ "robinhood-chain",
107
+ "sec-edgar"
108
+ ],
109
+ "author": "otto AI",
110
+ "license": "MIT",
111
+ "publishConfig": {
112
+ "access": "public"
113
+ },
114
+ "dependencies": {
115
+ "@modelcontextprotocol/sdk": "1.29.0",
116
+ "@nktkas/hyperliquid": "0.29.1",
117
+ "@polymarket/clob-client-v2": "1.0.0",
118
+ "@x402/axios": "2.23.0",
119
+ "@x402/evm": "2.23.0",
120
+ "axios": "^1.7.0",
121
+ "cors": "^2.8.5",
122
+ "dotenv": "^16.3.1",
123
+ "express": "^5.1.0",
124
+ "express-rate-limit": "^8.2.1",
125
+ "valibot": "1.2.0",
126
+ "viem": "2.55.2",
127
+ "zod": "^4.3.6"
128
+ },
129
+ "devDependencies": {
130
+ "@types/cors": "^2.8.19",
131
+ "@types/express": "^5.0.3",
132
+ "@types/node": "^22.0.0",
133
+ "tsx": "^4.7.0",
134
+ "typescript": "^5.3.3"
135
+ },
136
+ "overrides": {
137
+ "@hono/node-server": "2.0.10",
138
+ "@coinbase/cdp-sdk": {
139
+ "@x402/evm": "2.23.0",
140
+ "@x402/core": "2.23.0"
141
+ }
142
+ },
143
+ "exports": {
144
+ "./execution": {
145
+ "types": "./dist/execution-index.d.ts",
146
+ "import": "./dist/execution-index.js"
147
+ },
148
+ "./package.json": "./package.json",
149
+ "./adapter": {
150
+ "types": "./dist/adapter-index.d.ts",
151
+ "import": "./dist/adapter-index.js"
152
+ },
153
+ "./adapter-cdp": {
154
+ "types": "./dist/adapter-cdp-index.d.ts",
155
+ "import": "./dist/adapter-cdp-index.js"
156
+ }
157
+ },
158
+ "optionalDependencies": {
159
+ "@coinbase/cdp-sdk": "1.54.0"
160
+ },
161
+ "publishGate": "UNPUBLISHED (registry E404 as of 2026-08-29). The PR #2001 r1 MED is RESOLVED as of 2026-09-01: @x402/axios+evm are pinned 2.23.0 (>= the ^2.19.0 that @coinbase/cdp-sdk's optional peers want), so the graph now dedupes to a single @x402/core@2.23.0 and `npm ls` reports ZERO invalid/UNMET peers WITHOUT relying on the `overrides` pin \u2014 which npm honors in the root project only and does NOT inherit to consumers. The overrides entry is kept, bumped in lockstep, as belt-and-braces determinism for the Railway (root) install. NOTE (@x402 2.23.0 behavior change): client `spendControls` are now ON BY DEFAULT \u2014 recognized pegged assets only, $1 USD per-payment cap. Every tool price here is $0.001-$0.10, and a $0.10 Base-USDC payment was verified to build a payload on this exact client construction, so there is ~10x headroom; but any FUTURE tool price above $1 will be refused client-side unless `spendControls` is configured on the x402Client in src/x402-read-source.ts. 2.24.0 exists but is inside the harness's rolling 7-day supply-chain cooldown (`--before`), so 2.23.0 is the newest installable stable 2.x."
162
+ }