@one-source/api-mcp 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +147 -0
- package/dist/analytics.d.ts +53 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.fix-verification.test.d.ts +11 -0
- package/dist/analytics.fix-verification.test.d.ts.map +1 -0
- package/dist/analytics.fix-verification.test.js +146 -0
- package/dist/analytics.fix-verification.test.js.map +1 -0
- package/dist/analytics.js +126 -0
- package/dist/analytics.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +144 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +52 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +126 -0
- package/dist/client.js.map +1 -0
- package/dist/create-server.d.ts +28 -0
- package/dist/create-server.d.ts.map +1 -0
- package/dist/create-server.js +133 -0
- package/dist/create-server.js.map +1 -0
- package/dist/tools/chain/contract-code.d.ts +6 -0
- package/dist/tools/chain/contract-code.d.ts.map +1 -0
- package/dist/tools/chain/contract-code.js +19 -0
- package/dist/tools/chain/contract-code.js.map +1 -0
- package/dist/tools/chain/ens-resolve.d.ts +3 -0
- package/dist/tools/chain/ens-resolve.d.ts.map +1 -0
- package/dist/tools/chain/ens-resolve.js +25 -0
- package/dist/tools/chain/ens-resolve.js.map +1 -0
- package/dist/tools/chain/estimate-gas.d.ts +6 -0
- package/dist/tools/chain/estimate-gas.d.ts.map +1 -0
- package/dist/tools/chain/estimate-gas.js +31 -0
- package/dist/tools/chain/estimate-gas.js.map +1 -0
- package/dist/tools/chain/network-info.d.ts +6 -0
- package/dist/tools/chain/network-info.d.ts.map +1 -0
- package/dist/tools/chain/network-info.js +18 -0
- package/dist/tools/chain/network-info.js.map +1 -0
- package/dist/tools/chain/nonce.d.ts +6 -0
- package/dist/tools/chain/nonce.d.ts.map +1 -0
- package/dist/tools/chain/nonce.js +19 -0
- package/dist/tools/chain/nonce.js.map +1 -0
- package/dist/tools/chain/pending-block.d.ts +6 -0
- package/dist/tools/chain/pending-block.d.ts.map +1 -0
- package/dist/tools/chain/pending-block.js +18 -0
- package/dist/tools/chain/pending-block.js.map +1 -0
- package/dist/tools/chain/proxy-detect.d.ts +6 -0
- package/dist/tools/chain/proxy-detect.d.ts.map +1 -0
- package/dist/tools/chain/proxy-detect.js +19 -0
- package/dist/tools/chain/proxy-detect.js.map +1 -0
- package/dist/tools/chain/simulate-call.d.ts +6 -0
- package/dist/tools/chain/simulate-call.d.ts.map +1 -0
- package/dist/tools/chain/simulate-call.js +33 -0
- package/dist/tools/chain/simulate-call.js.map +1 -0
- package/dist/tools/chain/storage-read.d.ts +3 -0
- package/dist/tools/chain/storage-read.d.ts.map +1 -0
- package/dist/tools/chain/storage-read.js +22 -0
- package/dist/tools/chain/storage-read.js.map +1 -0
- package/dist/tools/chain/tx-receipt.d.ts +6 -0
- package/dist/tools/chain/tx-receipt.d.ts.map +1 -0
- package/dist/tools/chain/tx-receipt.js +18 -0
- package/dist/tools/chain/tx-receipt.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +77 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/indexed/address-txs.d.ts +6 -0
- package/dist/tools/indexed/address-txs.d.ts.map +1 -0
- package/dist/tools/indexed/address-txs.js +25 -0
- package/dist/tools/indexed/address-txs.js.map +1 -0
- package/dist/tools/indexed/block.d.ts +3 -0
- package/dist/tools/indexed/block.d.ts.map +1 -0
- package/dist/tools/indexed/block.js +23 -0
- package/dist/tools/indexed/block.js.map +1 -0
- package/dist/tools/indexed/contract-info.d.ts +6 -0
- package/dist/tools/indexed/contract-info.d.ts.map +1 -0
- package/dist/tools/indexed/contract-info.js +18 -0
- package/dist/tools/indexed/contract-info.js.map +1 -0
- package/dist/tools/indexed/erc1155-balance.d.ts +6 -0
- package/dist/tools/indexed/erc1155-balance.d.ts.map +1 -0
- package/dist/tools/indexed/erc1155-balance.js +24 -0
- package/dist/tools/indexed/erc1155-balance.js.map +1 -0
- package/dist/tools/indexed/erc20-balance.d.ts +6 -0
- package/dist/tools/indexed/erc20-balance.d.ts.map +1 -0
- package/dist/tools/indexed/erc20-balance.js +22 -0
- package/dist/tools/indexed/erc20-balance.js.map +1 -0
- package/dist/tools/indexed/erc20-transfers.d.ts +6 -0
- package/dist/tools/indexed/erc20-transfers.d.ts.map +1 -0
- package/dist/tools/indexed/erc20-transfers.js +24 -0
- package/dist/tools/indexed/erc20-transfers.js.map +1 -0
- package/dist/tools/indexed/events.d.ts +3 -0
- package/dist/tools/indexed/events.d.ts.map +1 -0
- package/dist/tools/indexed/events.js +32 -0
- package/dist/tools/indexed/events.js.map +1 -0
- package/dist/tools/indexed/nft-media.d.ts +6 -0
- package/dist/tools/indexed/nft-media.d.ts.map +1 -0
- package/dist/tools/indexed/nft-media.js +22 -0
- package/dist/tools/indexed/nft-media.js.map +1 -0
- package/dist/tools/indexed/nft-metadata.d.ts +6 -0
- package/dist/tools/indexed/nft-metadata.d.ts.map +1 -0
- package/dist/tools/indexed/nft-metadata.js +22 -0
- package/dist/tools/indexed/nft-metadata.js.map +1 -0
- package/dist/tools/indexed/nft-owner.d.ts +6 -0
- package/dist/tools/indexed/nft-owner.d.ts.map +1 -0
- package/dist/tools/indexed/nft-owner.js +22 -0
- package/dist/tools/indexed/nft-owner.js.map +1 -0
- package/dist/tools/indexed/tx-details.d.ts +6 -0
- package/dist/tools/indexed/tx-details.d.ts.map +1 -0
- package/dist/tools/indexed/tx-details.js +19 -0
- package/dist/tools/indexed/tx-details.js.map +1 -0
- package/dist/tools/indexed/wallet-nfts.d.ts +6 -0
- package/dist/tools/indexed/wallet-nfts.d.ts.map +1 -0
- package/dist/tools/indexed/wallet-nfts.js +22 -0
- package/dist/tools/indexed/wallet-nfts.js.map +1 -0
- package/dist/tools/live/allowance.d.ts +6 -0
- package/dist/tools/live/allowance.d.ts.map +1 -0
- package/dist/tools/live/allowance.js +24 -0
- package/dist/tools/live/allowance.js.map +1 -0
- package/dist/tools/live/contract-info.d.ts +6 -0
- package/dist/tools/live/contract-info.d.ts.map +1 -0
- package/dist/tools/live/contract-info.js +19 -0
- package/dist/tools/live/contract-info.js.map +1 -0
- package/dist/tools/live/erc1155-balance.d.ts +6 -0
- package/dist/tools/live/erc1155-balance.d.ts.map +1 -0
- package/dist/tools/live/erc1155-balance.js +24 -0
- package/dist/tools/live/erc1155-balance.js.map +1 -0
- package/dist/tools/live/erc20-balance.d.ts +6 -0
- package/dist/tools/live/erc20-balance.d.ts.map +1 -0
- package/dist/tools/live/erc20-balance.js +22 -0
- package/dist/tools/live/erc20-balance.js.map +1 -0
- package/dist/tools/live/erc20-transfers.d.ts +3 -0
- package/dist/tools/live/erc20-transfers.d.ts.map +1 -0
- package/dist/tools/live/erc20-transfers.js +26 -0
- package/dist/tools/live/erc20-transfers.js.map +1 -0
- package/dist/tools/live/erc721-tokens.d.ts +6 -0
- package/dist/tools/live/erc721-tokens.d.ts.map +1 -0
- package/dist/tools/live/erc721-tokens.js +22 -0
- package/dist/tools/live/erc721-tokens.js.map +1 -0
- package/dist/tools/live/events.d.ts +3 -0
- package/dist/tools/live/events.d.ts.map +1 -0
- package/dist/tools/live/events.js +26 -0
- package/dist/tools/live/events.js.map +1 -0
- package/dist/tools/live/multi-balance.d.ts +3 -0
- package/dist/tools/live/multi-balance.d.ts.map +1 -0
- package/dist/tools/live/multi-balance.js +26 -0
- package/dist/tools/live/multi-balance.js.map +1 -0
- package/dist/tools/live/nft-metadata.d.ts +6 -0
- package/dist/tools/live/nft-metadata.d.ts.map +1 -0
- package/dist/tools/live/nft-metadata.js +22 -0
- package/dist/tools/live/nft-metadata.js.map +1 -0
- package/dist/tools/live/nft-owner.d.ts +6 -0
- package/dist/tools/live/nft-owner.d.ts.map +1 -0
- package/dist/tools/live/nft-owner.js +22 -0
- package/dist/tools/live/nft-owner.js.map +1 -0
- package/dist/tools/live/total-supply.d.ts +6 -0
- package/dist/tools/live/total-supply.d.ts.map +1 -0
- package/dist/tools/live/total-supply.js +20 -0
- package/dist/tools/live/total-supply.js.map +1 -0
- package/dist/tools/live/tx-details.d.ts +6 -0
- package/dist/tools/live/tx-details.d.ts.map +1 -0
- package/dist/tools/live/tx-details.js +19 -0
- package/dist/tools/live/tx-details.js.map +1 -0
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +86 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 BlockParty
|
|
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,147 @@
|
|
|
1
|
+
# onesource-api-mcp
|
|
2
|
+
|
|
3
|
+
MCP server for OneSource blockchain data. 34 named tools for balances, NFTs, transactions, events, and live chain queries via x402 micropayments.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npx onesource-api-mcp
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
# Claude Code
|
|
13
|
+
claude mcp add onesource-api -- npx onesource-api-mcp
|
|
14
|
+
|
|
15
|
+
# Claude Desktop / Cursor — add to MCP config:
|
|
16
|
+
{
|
|
17
|
+
"mcpServers": {
|
|
18
|
+
"onesource-api": {
|
|
19
|
+
"command": "npx",
|
|
20
|
+
"args": ["-y", "onesource-api-mcp"]
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Tools (34)
|
|
27
|
+
|
|
28
|
+
### Indexed Data (12 tools) — OpenSearch, cheaper, slight sync delay
|
|
29
|
+
|
|
30
|
+
| Tool | Description |
|
|
31
|
+
|------|-------------|
|
|
32
|
+
| `1s_address_txs` | Address transaction history |
|
|
33
|
+
| `1s_block` | Block details by number |
|
|
34
|
+
| `1s_contract_info` | Contract metadata |
|
|
35
|
+
| `1s_erc1155_balance` | ERC1155 token balance |
|
|
36
|
+
| `1s_erc20_balance` | ERC20 token balance |
|
|
37
|
+
| `1s_erc20_transfers` | ERC20 transfer history |
|
|
38
|
+
| `1s_events` | Event log search |
|
|
39
|
+
| `1s_nft_media` | NFT processed media URLs |
|
|
40
|
+
| `1s_nft_metadata` | NFT metadata and traits |
|
|
41
|
+
| `1s_nft_owner` | NFT current owner |
|
|
42
|
+
| `1s_tx_details` | Transaction with decoded events |
|
|
43
|
+
| `1s_wallet_nfts` | Wallet NFT portfolio |
|
|
44
|
+
|
|
45
|
+
### Live Chain Data (12 tools) — Direct RPC, real-time
|
|
46
|
+
|
|
47
|
+
| Tool | Description |
|
|
48
|
+
|------|-------------|
|
|
49
|
+
| `1s_allowance_live` | ERC20 allowance check |
|
|
50
|
+
| `1s_contract_info_live` | Contract type detection via ERC165 |
|
|
51
|
+
| `1s_erc1155_balance_live` | ERC1155 balance via RPC |
|
|
52
|
+
| `1s_erc20_balance_live` | ERC20 balance via balanceOf |
|
|
53
|
+
| `1s_erc20_transfers_live` | ERC20 Transfer logs via eth_getLogs |
|
|
54
|
+
| `1s_erc721_tokens_live` | ERC721 token enumeration |
|
|
55
|
+
| `1s_events_live` | Event logs via eth_getLogs |
|
|
56
|
+
| `1s_multi_balance_live` | ETH + multiple ERC20 balances |
|
|
57
|
+
| `1s_nft_metadata_live` | NFT metadata via tokenURI |
|
|
58
|
+
| `1s_nft_owner_live` | NFT owner via ownerOf |
|
|
59
|
+
| `1s_total_supply_live` | Token total supply |
|
|
60
|
+
| `1s_tx_details_live` | Transaction + receipt via RPC |
|
|
61
|
+
|
|
62
|
+
### Chain Utilities (10 tools) — RPC only
|
|
63
|
+
|
|
64
|
+
| Tool | Description |
|
|
65
|
+
|------|-------------|
|
|
66
|
+
| `1s_contract_code` | Contract bytecode |
|
|
67
|
+
| `1s_ens_resolve` | ENS name/address resolution |
|
|
68
|
+
| `1s_estimate_gas` | Gas estimation |
|
|
69
|
+
| `1s_network_info` | Chain ID, block number, gas price |
|
|
70
|
+
| `1s_nonce` | Transaction count |
|
|
71
|
+
| `1s_pending_block` | Pending block from mempool |
|
|
72
|
+
| `1s_proxy_detect` | Proxy contract detection |
|
|
73
|
+
| `1s_simulate_call` | Simulate eth_call |
|
|
74
|
+
| `1s_storage_read` | Read storage slot |
|
|
75
|
+
| `1s_tx_receipt` | Transaction receipt |
|
|
76
|
+
|
|
77
|
+
## Networks
|
|
78
|
+
|
|
79
|
+
All tools accept an optional `network` parameter:
|
|
80
|
+
|
|
81
|
+
| Network | Description |
|
|
82
|
+
|---------|-------------|
|
|
83
|
+
| `ethereum` | Ethereum mainnet (default) |
|
|
84
|
+
| `sepolia` | Ethereum Sepolia testnet |
|
|
85
|
+
| `avax` | Avalanche C-Chain |
|
|
86
|
+
|
|
87
|
+
## Payment (x402)
|
|
88
|
+
|
|
89
|
+
Endpoints are priced in USDC on Base via [x402](https://github.com/coinbase/x402). When the backend has payments enabled, tool calls return a 402 with payment details. Agents using [`@x402/fetch`](https://www.npmjs.com/package/@x402/fetch) handle this automatically.
|
|
90
|
+
|
|
91
|
+
## Configuration
|
|
92
|
+
|
|
93
|
+
| Variable | Default | Description |
|
|
94
|
+
|----------|---------|-------------|
|
|
95
|
+
| `ONESOURCE_BASE_URL` | `https://skills.onesource.io` | Skills API endpoint |
|
|
96
|
+
| `ONESOURCE_ANALYTICS` | `true` | Set `false` to disable analytics |
|
|
97
|
+
| `ONESOURCE_ANALYTICS_URL` | — | Dashboard endpoint for event ingestion |
|
|
98
|
+
| `ONESOURCE_ANALYTICS_KEY` | — | API key for the analytics dashboard |
|
|
99
|
+
|
|
100
|
+
## Transport Modes
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
# stdio (default — for MCP clients)
|
|
104
|
+
npx onesource-api-mcp
|
|
105
|
+
|
|
106
|
+
# HTTP (for testing or remote access)
|
|
107
|
+
npx onesource-api-mcp --http
|
|
108
|
+
npx onesource-api-mcp --http --port=8080
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
HTTP mode binds to `127.0.0.1` by default.
|
|
112
|
+
|
|
113
|
+
## Adding a New Tool
|
|
114
|
+
|
|
115
|
+
When a new SKILL.md is added to `skills/OneSource*/`, a corresponding MCP tool file needs to be created here.
|
|
116
|
+
|
|
117
|
+
**Step 1:** Create the SKILL.md in the skills repo (see the skills README for format).
|
|
118
|
+
|
|
119
|
+
**Step 2:** Scaffold the tool file:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
cd mcp/
|
|
123
|
+
npm run new-tool
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This prompts for:
|
|
127
|
+
- **Tool name** — `1s_` prefix, snake_case (e.g. `1s_token_info`)
|
|
128
|
+
- **Category** — `indexed` (OpenSearch), `live` (RPC with indexed counterpart), or `chain` (RPC-only utility)
|
|
129
|
+
- **Description** — one-liner for the tool
|
|
130
|
+
- **Endpoint** — REST API path (e.g. `/api/chain/my-endpoint`)
|
|
131
|
+
- **Method** — GET or POST
|
|
132
|
+
|
|
133
|
+
It generates a tool file in `src/tools/{category}/` with TODO markers.
|
|
134
|
+
|
|
135
|
+
**Step 3:** Edit the generated file — fill in the Zod schema parameters and handler logic. Use the shared params from `src/types.ts` (`addressParam`, `txHashParam`, `tokenIdParam`, `hexDataParam`, `networkParam`, `limitParam`, `blockNumberParam`).
|
|
136
|
+
|
|
137
|
+
**Step 4:** Build:
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
npm run build
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
This auto-regenerates the tool index and compiles. The new tool is immediately available in the MCP server.
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics instrumentation for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Three modes (selected via env vars):
|
|
5
|
+
* - NoopAnalytics: ONESOURCE_ANALYTICS=false
|
|
6
|
+
* - StderrAnalytics: default (structured JSON to stderr)
|
|
7
|
+
* - DashboardAnalytics: ONESOURCE_ANALYTICS_URL is set (POSTs to dashboard)
|
|
8
|
+
*/
|
|
9
|
+
export interface ToolCallEvent {
|
|
10
|
+
type: 'tool_call';
|
|
11
|
+
service: string;
|
|
12
|
+
tool: string;
|
|
13
|
+
category: 'indexed' | 'live' | 'chain';
|
|
14
|
+
timestamp: string;
|
|
15
|
+
duration_ms: number;
|
|
16
|
+
success: boolean;
|
|
17
|
+
error_category?: string;
|
|
18
|
+
network?: string;
|
|
19
|
+
input_params: string[];
|
|
20
|
+
response_size: number;
|
|
21
|
+
version: string;
|
|
22
|
+
auth_method: 'x402' | 'none';
|
|
23
|
+
client_name?: string;
|
|
24
|
+
client_version?: string;
|
|
25
|
+
session_id?: string;
|
|
26
|
+
transport?: 'stdio' | 'http';
|
|
27
|
+
}
|
|
28
|
+
export interface HttpCallEvent {
|
|
29
|
+
type: 'http_call';
|
|
30
|
+
service: string;
|
|
31
|
+
tool: string;
|
|
32
|
+
http_status: number;
|
|
33
|
+
backend_latency_ms: number;
|
|
34
|
+
x402_required: boolean;
|
|
35
|
+
timestamp: string;
|
|
36
|
+
session_id?: string;
|
|
37
|
+
}
|
|
38
|
+
export interface ServiceEvent {
|
|
39
|
+
type: 'service_start' | 'service_stop' | 'service_error';
|
|
40
|
+
service: string;
|
|
41
|
+
timestamp: string;
|
|
42
|
+
version: string;
|
|
43
|
+
details?: string;
|
|
44
|
+
}
|
|
45
|
+
export interface Analytics {
|
|
46
|
+
trackTool(event: ToolCallEvent): void;
|
|
47
|
+
trackHttp(event: HttpCallEvent): void;
|
|
48
|
+
trackService(event: ServiceEvent): void;
|
|
49
|
+
flush(): Promise<void>;
|
|
50
|
+
stop(): void;
|
|
51
|
+
}
|
|
52
|
+
export declare function createAnalytics(): Analytics;
|
|
53
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,MAAM,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,eAAe,GAAG,cAAc,GAAG,eAAe,CAAC;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACtC,SAAS,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACtC,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACxC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,IAAI,CAAC;CACd;AAkHD,wBAAgB,eAAe,IAAI,SAAS,CAY3C"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification tests for DashboardAnalytics bug fixes:
|
|
3
|
+
*
|
|
4
|
+
* 1. Event preservation — events are restored to the buffer when flush fails,
|
|
5
|
+
* so they can be retried on the next flush.
|
|
6
|
+
*
|
|
7
|
+
* 2. Flush timeout — flush() aborts after 3s via AbortSignal.timeout,
|
|
8
|
+
* so shutdown never hangs indefinitely.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=analytics.fix-verification.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.fix-verification.test.d.ts","sourceRoot":"","sources":["../src/analytics.fix-verification.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verification tests for DashboardAnalytics bug fixes:
|
|
3
|
+
*
|
|
4
|
+
* 1. Event preservation — events are restored to the buffer when flush fails,
|
|
5
|
+
* so they can be retried on the next flush.
|
|
6
|
+
*
|
|
7
|
+
* 2. Flush timeout — flush() aborts after 3s via AbortSignal.timeout,
|
|
8
|
+
* so shutdown never hangs indefinitely.
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
11
|
+
import { createAnalytics } from './analytics.js';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Helpers
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
function makeToolEvent(overrides = {}) {
|
|
16
|
+
return {
|
|
17
|
+
type: 'tool_call',
|
|
18
|
+
service: 'mcp',
|
|
19
|
+
tool: 'test-tool',
|
|
20
|
+
category: 'indexed',
|
|
21
|
+
timestamp: new Date().toISOString(),
|
|
22
|
+
duration_ms: 50,
|
|
23
|
+
success: true,
|
|
24
|
+
input_params: [],
|
|
25
|
+
response_size: 128,
|
|
26
|
+
version: '1.0.0',
|
|
27
|
+
auth_method: 'none',
|
|
28
|
+
...overrides,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
// Drain the microtask queue — works regardless of timer mode.
|
|
32
|
+
const drainMicrotasks = async () => {
|
|
33
|
+
for (let i = 0; i < 5; i++)
|
|
34
|
+
await Promise.resolve();
|
|
35
|
+
};
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Setup: point createAnalytics() at DashboardAnalytics via env vars
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
process.env.ONESOURCE_ANALYTICS_URL = 'http://analytics.test';
|
|
41
|
+
process.env.ONESOURCE_ANALYTICS_KEY = 'test-key';
|
|
42
|
+
vi.spyOn(global, 'fetch');
|
|
43
|
+
});
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
delete process.env.ONESOURCE_ANALYTICS_URL;
|
|
46
|
+
delete process.env.ONESOURCE_ANALYTICS_KEY;
|
|
47
|
+
vi.restoreAllMocks();
|
|
48
|
+
});
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Fix 1: Events preserved on network failure
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
describe('Event preservation on network failure', () => {
|
|
53
|
+
it('events are restored to buffer on failure and retried on next flush', async () => {
|
|
54
|
+
vi.mocked(global.fetch).mockRejectedValue(new Error('Network failure'));
|
|
55
|
+
const analytics = createAnalytics();
|
|
56
|
+
for (let i = 0; i < 10; i++) {
|
|
57
|
+
analytics.trackTool(makeToolEvent());
|
|
58
|
+
}
|
|
59
|
+
// Let the auto-triggered flush run: splice(0) removes events, fetch rejects,
|
|
60
|
+
// catch restores them to the buffer via unshift.
|
|
61
|
+
await drainMicrotasks();
|
|
62
|
+
expect(global.fetch).toHaveBeenCalledOnce();
|
|
63
|
+
// Network is healthy again
|
|
64
|
+
vi.mocked(global.fetch).mockResolvedValue(new Response(JSON.stringify({ ok: true }), { status: 200 }));
|
|
65
|
+
// Manually flush — the 10 events were preserved and are now sent
|
|
66
|
+
await analytics.flush();
|
|
67
|
+
// fetch called twice — once failed, once succeeded with the restored events
|
|
68
|
+
expect(global.fetch).toHaveBeenCalledTimes(2);
|
|
69
|
+
});
|
|
70
|
+
it('flush-in-progress guard prevents concurrent flushes, events preserved on failure', async () => {
|
|
71
|
+
let rejectFetch;
|
|
72
|
+
vi.mocked(global.fetch).mockImplementation(() => {
|
|
73
|
+
return new Promise((_, reject) => { rejectFetch = reject; });
|
|
74
|
+
});
|
|
75
|
+
const analytics = createAnalytics();
|
|
76
|
+
// First batch of 10 → triggers flush
|
|
77
|
+
for (let i = 0; i < 10; i++) {
|
|
78
|
+
analytics.trackTool(makeToolEvent());
|
|
79
|
+
}
|
|
80
|
+
await drainMicrotasks();
|
|
81
|
+
expect(global.fetch).toHaveBeenCalledOnce();
|
|
82
|
+
// Second batch of 10 arrives while flush is in-flight
|
|
83
|
+
// The flush-in-progress guard means these stay in the buffer —
|
|
84
|
+
// no second concurrent fetch fires
|
|
85
|
+
for (let i = 0; i < 10; i++) {
|
|
86
|
+
analytics.trackTool(makeToolEvent());
|
|
87
|
+
}
|
|
88
|
+
await drainMicrotasks();
|
|
89
|
+
// Still only 1 fetch — guard prevented a concurrent flush
|
|
90
|
+
expect(global.fetch).toHaveBeenCalledOnce();
|
|
91
|
+
// Fail the in-flight request — first 10 events restored via unshift
|
|
92
|
+
rejectFetch(new Error('timeout'));
|
|
93
|
+
await drainMicrotasks();
|
|
94
|
+
// Restore healthy network
|
|
95
|
+
vi.mocked(global.fetch).mockResolvedValue(new Response(JSON.stringify({ ok: true }), { status: 200 }));
|
|
96
|
+
// Flush now sends all 20 events (10 restored + 10 buffered)
|
|
97
|
+
await analytics.flush();
|
|
98
|
+
// 2 calls total — one failed, one succeeded with all 20 events
|
|
99
|
+
expect(global.fetch).toHaveBeenCalledTimes(2);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Fix 2: Flush timeout prevents shutdown hang
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
describe('Flush timeout prevents shutdown hang', () => {
|
|
106
|
+
it('flush() resolves within timeout even when fetch hangs', async () => {
|
|
107
|
+
// Mock fetch that respects AbortSignal — hangs until aborted
|
|
108
|
+
vi.mocked(global.fetch).mockImplementation((_url, init) => {
|
|
109
|
+
return new Promise((_, reject) => {
|
|
110
|
+
const signal = init?.signal;
|
|
111
|
+
if (signal) {
|
|
112
|
+
signal.addEventListener('abort', () => reject(signal.reason));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
const analytics = createAnalytics();
|
|
117
|
+
analytics.trackTool(makeToolEvent());
|
|
118
|
+
let flushCompleted = false;
|
|
119
|
+
await Promise.race([
|
|
120
|
+
analytics.flush().then(() => { flushCompleted = true; }),
|
|
121
|
+
// 4s deadline — longer than the 3s AbortSignal.timeout in flush()
|
|
122
|
+
new Promise((resolve) => setTimeout(resolve, 4_000)),
|
|
123
|
+
]);
|
|
124
|
+
// flush completed because AbortSignal.timeout aborted the hung fetch
|
|
125
|
+
expect(flushCompleted).toBe(true);
|
|
126
|
+
}, 10_000);
|
|
127
|
+
it('a single buffered event does not hang the process on shutdown', async () => {
|
|
128
|
+
vi.mocked(global.fetch).mockImplementation((_url, init) => {
|
|
129
|
+
return new Promise((_, reject) => {
|
|
130
|
+
const signal = init?.signal;
|
|
131
|
+
if (signal) {
|
|
132
|
+
signal.addEventListener('abort', () => reject(signal.reason));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
const analytics = createAnalytics();
|
|
137
|
+
analytics.trackTool(makeToolEvent());
|
|
138
|
+
const result = await Promise.race([
|
|
139
|
+
analytics.flush().then(() => 'completed'),
|
|
140
|
+
new Promise((resolve) => setTimeout(() => resolve('timed-out'), 4_000)),
|
|
141
|
+
]);
|
|
142
|
+
// flush completes (via abort timeout) rather than hanging
|
|
143
|
+
expect(result).toBe('completed');
|
|
144
|
+
}, 10_000);
|
|
145
|
+
});
|
|
146
|
+
//# sourceMappingURL=analytics.fix-verification.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.fix-verification.test.js","sourceRoot":"","sources":["../src/analytics.fix-verification.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Analytics instrumentation for the MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Three modes (selected via env vars):
|
|
5
|
+
* - NoopAnalytics: ONESOURCE_ANALYTICS=false
|
|
6
|
+
* - StderrAnalytics: default (structured JSON to stderr)
|
|
7
|
+
* - DashboardAnalytics: ONESOURCE_ANALYTICS_URL is set (POSTs to dashboard)
|
|
8
|
+
*/
|
|
9
|
+
// --- Implementations ---
|
|
10
|
+
class NoopAnalytics {
|
|
11
|
+
trackTool() { }
|
|
12
|
+
trackHttp() { }
|
|
13
|
+
trackService() { }
|
|
14
|
+
stop() { }
|
|
15
|
+
async flush() { }
|
|
16
|
+
}
|
|
17
|
+
class StderrAnalytics {
|
|
18
|
+
trackTool(event) {
|
|
19
|
+
console.error(JSON.stringify(event));
|
|
20
|
+
}
|
|
21
|
+
trackHttp(event) {
|
|
22
|
+
console.error(JSON.stringify(event));
|
|
23
|
+
}
|
|
24
|
+
trackService(event) {
|
|
25
|
+
console.error(JSON.stringify(event));
|
|
26
|
+
}
|
|
27
|
+
stop() { }
|
|
28
|
+
async flush() { }
|
|
29
|
+
}
|
|
30
|
+
class DashboardAnalytics {
|
|
31
|
+
static FLUSH_TIMEOUT_MS = 3_000;
|
|
32
|
+
static MAX_BUFFER = 1_000;
|
|
33
|
+
buffer = [];
|
|
34
|
+
flushPromise = null;
|
|
35
|
+
timer = null;
|
|
36
|
+
endpoint;
|
|
37
|
+
apiKey;
|
|
38
|
+
constructor(endpoint, apiKey) {
|
|
39
|
+
const parsed = new URL(endpoint);
|
|
40
|
+
if (!['https:', 'http:'].includes(parsed.protocol)) {
|
|
41
|
+
throw new Error('ONESOURCE_ANALYTICS_URL must use http or https');
|
|
42
|
+
}
|
|
43
|
+
this.endpoint = endpoint.replace(/\/+$/, '');
|
|
44
|
+
this.apiKey = apiKey;
|
|
45
|
+
// Auto-flush every 30 seconds
|
|
46
|
+
this.timer = setInterval(() => { void this.flush(); }, 30_000);
|
|
47
|
+
// Don't keep the process alive just for the flush timer
|
|
48
|
+
if (this.timer.unref)
|
|
49
|
+
this.timer.unref();
|
|
50
|
+
}
|
|
51
|
+
stop() {
|
|
52
|
+
if (this.timer) {
|
|
53
|
+
clearInterval(this.timer);
|
|
54
|
+
this.timer = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
trackTool(event) {
|
|
58
|
+
this.buffer.push(event);
|
|
59
|
+
if (this.buffer.length >= 10)
|
|
60
|
+
void this.flush();
|
|
61
|
+
}
|
|
62
|
+
trackHttp(event) {
|
|
63
|
+
this.buffer.push(event);
|
|
64
|
+
if (this.buffer.length >= 10)
|
|
65
|
+
void this.flush();
|
|
66
|
+
}
|
|
67
|
+
trackService(event) {
|
|
68
|
+
this.buffer.push(event);
|
|
69
|
+
}
|
|
70
|
+
async flush() {
|
|
71
|
+
// If a flush is already in-flight, await it instead of racing
|
|
72
|
+
if (this.flushPromise)
|
|
73
|
+
return this.flushPromise;
|
|
74
|
+
if (this.buffer.length === 0)
|
|
75
|
+
return;
|
|
76
|
+
this.flushPromise = this.doFlush();
|
|
77
|
+
try {
|
|
78
|
+
await this.flushPromise;
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
this.flushPromise = null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async doFlush() {
|
|
85
|
+
const events = this.buffer.splice(0);
|
|
86
|
+
try {
|
|
87
|
+
const res = await fetch(`${this.endpoint}/api/events`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
'Authorization': `Bearer ${this.apiKey}`,
|
|
92
|
+
'x-402-receipt': this.apiKey,
|
|
93
|
+
},
|
|
94
|
+
body: JSON.stringify({ events }),
|
|
95
|
+
signal: AbortSignal.timeout(DashboardAnalytics.FLUSH_TIMEOUT_MS),
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
console.error(`[analytics] flush failed: HTTP ${res.status}`);
|
|
99
|
+
this.buffer = events.concat(this.buffer);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
console.error(`[analytics] flush failed: ${events.length} events queued for retry`);
|
|
104
|
+
this.buffer = events.concat(this.buffer);
|
|
105
|
+
}
|
|
106
|
+
// Cap buffer to prevent unbounded growth — drop oldest first
|
|
107
|
+
if (this.buffer.length > DashboardAnalytics.MAX_BUFFER) {
|
|
108
|
+
const dropped = this.buffer.length - DashboardAnalytics.MAX_BUFFER;
|
|
109
|
+
this.buffer.splice(0, dropped);
|
|
110
|
+
console.error(`[analytics] buffer full: dropped ${dropped} oldest events`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
// --- Factory ---
|
|
115
|
+
export function createAnalytics() {
|
|
116
|
+
if (process.env.ONESOURCE_ANALYTICS === 'false') {
|
|
117
|
+
return new NoopAnalytics();
|
|
118
|
+
}
|
|
119
|
+
const url = process.env.ONESOURCE_ANALYTICS_URL?.trim();
|
|
120
|
+
const key = process.env.X402_ANALYTICS_KEY?.trim();
|
|
121
|
+
if (url && key) {
|
|
122
|
+
return new DashboardAnalytics(url, key);
|
|
123
|
+
}
|
|
124
|
+
return new StderrAnalytics();
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAoKA,OAAO,EAAE,CAAC"}
|