@pipeworx/mcp-gsa-auctions 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 +21 -0
- package/README.md +55 -0
- package/package.json +20 -0
- package/server.json +18 -0
- package/src/index.ts +308 -0
- package/tsconfig.json +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pipeworx
|
|
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,55 @@
|
|
|
1
|
+
# mcp-gsa-auctions
|
|
2
|
+
|
|
3
|
+
GSA Auctions API MCP — US federal government surplus auctions (keyed).
|
|
4
|
+
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 1193+ live data sources.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"gsa-auctions": {
|
|
20
|
+
"url": "https://gateway.pipeworx.io/gsa-auctions/mcp"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or connect to the full Pipeworx gateway for access to all 1193+ data sources:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"pipeworx": {
|
|
32
|
+
"url": "https://gateway.pipeworx.io/mcp"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Using with ask_pipeworx
|
|
39
|
+
|
|
40
|
+
Instead of calling tools directly, you can ask questions in plain English:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
ask_pipeworx({ question: "your question about Gsa Auctions data" })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The gateway picks the right tool and fills the arguments automatically.
|
|
47
|
+
|
|
48
|
+
## More
|
|
49
|
+
|
|
50
|
+
- [All tools and guides](https://github.com/pipeworx-io/examples)
|
|
51
|
+
- [pipeworx.io](https://pipeworx.io)
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipeworx/mcp-gsa-auctions",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "GSA Auctions API MCP — US federal government surplus auctions (keyed).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "gsa-auctions"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-gsa-auctions"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.pipeworx-io/gsa-auctions",
|
|
4
|
+
"title": "Gsa Auctions",
|
|
5
|
+
"description": "GSA Auctions API MCP — US federal government surplus auctions (keyed).",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/gsa-auctions",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-gsa-auctions",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/gsa-auctions/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
interface McpToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface McpToolExport {
|
|
12
|
+
tools: McpToolDefinition[];
|
|
13
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
meter?: { credits: number };
|
|
15
|
+
cost?: Record<string, unknown>;
|
|
16
|
+
provider?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* GSA Auctions API MCP — US federal government surplus auctions (keyed).
|
|
21
|
+
*
|
|
22
|
+
* Wraps the GSA Auctions v2 API on the api.data.gov umbrella:
|
|
23
|
+
* https://api.gsa.gov/assets/gsaauctions/v2/auctions
|
|
24
|
+
* GSA Auctions sells federal surplus property to the public — vehicles,
|
|
25
|
+
* machinery, aircraft, vessels, industrial equipment, electronics, and more,
|
|
26
|
+
* contributed by every participating agency.
|
|
27
|
+
*
|
|
28
|
+
* Auth: the endpoint requires an api.data.gov key. The Pipeworx gateway injects
|
|
29
|
+
* the platform key as `args._apiKey`; callers may BYO via `?_apiKey=`. The key is
|
|
30
|
+
* sent both as `?api_key=` (api.data.gov umbrella convention) and the `X-Api-Key`
|
|
31
|
+
* header (the upstream OpenAPI scheme). If no key is present, tools return a
|
|
32
|
+
* clear { error } instructing how to supply one.
|
|
33
|
+
*
|
|
34
|
+
* The upstream API is a single GET that returns ALL current auction listings as
|
|
35
|
+
* one array with NO server-side filter params (only `format`). So state / status /
|
|
36
|
+
* keyword filtering, closing-soon sorting, and single-lot lookup are all done
|
|
37
|
+
* client-side over that array. Tools return shaped, LLM-friendly objects and never
|
|
38
|
+
* throw — fetch/parse failures resolve to { error }.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
const BASE = 'https://api.gsa.gov/assets/gsaauctions/v2/auctions';
|
|
43
|
+
const UA = 'pipeworx/1.0 (+https://pipeworx.io)';
|
|
44
|
+
|
|
45
|
+
const tools: McpToolExport['tools'] = [
|
|
46
|
+
{
|
|
47
|
+
name: 'gsa_search_auctions',
|
|
48
|
+
description:
|
|
49
|
+
'Search current US federal government surplus auctions from GSA Auctions (api.data.gov). GSA Auctions sells government surplus to the public — vehicles, machinery, aircraft, vessels, industrial equipment, electronics, etc. Filter active lots by property state (2-letter, e.g. "TX"), a free-text keyword matched against the item name (e.g. "truck", "forklift", "boat"), and/or auction status. Returns each lot with sale/lot number, item name, property location, sale location, start/end dates, status, high bid, reserve, number of bidders, item and image URLs. NOTE: the upstream API has no server-side category filter — use the keyword argument to narrow by item type.',
|
|
50
|
+
inputSchema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
state: { type: 'string', description: 'Filter by 2-letter property state code (e.g. "TX", "CA"). Matches PropertyState.' },
|
|
54
|
+
keyword: { type: 'string', description: 'Free-text keyword matched (case-insensitive) against the item name — use for category/type (e.g. "truck", "forklift", "aircraft", "boat", "generator").' },
|
|
55
|
+
status: { type: 'string', description: 'Filter by AuctionStatus (case-insensitive substring, e.g. "Active", "Open"). Omit to include all statuses.' },
|
|
56
|
+
limit: { type: ['number', 'string'], description: 'Max lots to return (1-200). Default 25.' },
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'gsa_auction_lot',
|
|
62
|
+
description:
|
|
63
|
+
'Full detail for a single GSA Auctions lot, identified by its sale number and lot number. Returns everything the auction listing carries: item name, per-lot descriptions (LotInfo), property and sale locations, start/end dates, status, high bid, reserve, bid increment, bidder count, inspection instructions, contracting-officer contact, agency/bureau, and item/image URLs.',
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
sale_no: { type: 'string', description: 'The sale number (SaleNo), e.g. "25QSCI21001".' },
|
|
68
|
+
lot_no: { type: ['number', 'string'], description: 'The lot number (LotNo) within that sale, e.g. 1.' },
|
|
69
|
+
},
|
|
70
|
+
required: ['sale_no', 'lot_no'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'gsa_auctions_closing_soon',
|
|
75
|
+
description:
|
|
76
|
+
'Current GSA Auctions federal surplus lots that are ending soonest — active auctions sorted by auction end date ascending. Use to find last-chance bidding opportunities across all agencies and property types. Returns sale/lot number, item name, location, end date, status, high bid, and reserve.',
|
|
77
|
+
inputSchema: {
|
|
78
|
+
type: 'object',
|
|
79
|
+
properties: {
|
|
80
|
+
limit: { type: ['number', 'string'], description: 'Max lots to return (1-200). Default 25.' },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
87
|
+
try {
|
|
88
|
+
// Gateway injects the platform api.data.gov key here (or caller BYOs via ?_apiKey=).
|
|
89
|
+
const apiKey = args._apiKey as string | undefined;
|
|
90
|
+
delete args._apiKey;
|
|
91
|
+
if (!apiKey) {
|
|
92
|
+
return {
|
|
93
|
+
error:
|
|
94
|
+
'GSA Auctions requires an api.data.gov key — contact the operator, or BYO via ?_apiKey=<your api.data.gov key> (get one free at https://api.data.gov/signup/).',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
switch (name) {
|
|
98
|
+
case 'gsa_search_auctions':
|
|
99
|
+
return await searchAuctions(args, apiKey);
|
|
100
|
+
case 'gsa_auction_lot':
|
|
101
|
+
return await auctionLot(args, apiKey);
|
|
102
|
+
case 'gsa_auctions_closing_soon':
|
|
103
|
+
return await closingSoon(args, apiKey);
|
|
104
|
+
default:
|
|
105
|
+
return { error: `Unknown tool: ${name}` };
|
|
106
|
+
}
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function searchAuctions(args: Record<string, unknown>, apiKey: string): Promise<unknown> {
|
|
113
|
+
const all = await fetchAuctions(apiKey);
|
|
114
|
+
const state = strArg(args.state)?.toUpperCase();
|
|
115
|
+
const keyword = strArg(args.keyword)?.toLowerCase();
|
|
116
|
+
const status = strArg(args.status)?.toLowerCase();
|
|
117
|
+
const limit = clampLimit(args.limit, 25);
|
|
118
|
+
|
|
119
|
+
let lots = all;
|
|
120
|
+
if (state) lots = lots.filter((a) => (a.PropertyState ?? '').toUpperCase() === state);
|
|
121
|
+
if (keyword) lots = lots.filter((a) => (a.ItemName ?? '').toLowerCase().includes(keyword));
|
|
122
|
+
if (status) lots = lots.filter((a) => (a.AuctionStatus ?? '').toLowerCase().includes(status));
|
|
123
|
+
|
|
124
|
+
const results = lots.slice(0, limit).map(shapeSummary);
|
|
125
|
+
return {
|
|
126
|
+
total_matched: lots.length,
|
|
127
|
+
returned: results.length,
|
|
128
|
+
filters: { state: state ?? null, keyword: keyword ?? null, status: status ?? null },
|
|
129
|
+
lots: results,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function auctionLot(args: Record<string, unknown>, apiKey: string): Promise<unknown> {
|
|
134
|
+
const saleNo = strArg(args.sale_no);
|
|
135
|
+
const lotNo = strArg(args.lot_no);
|
|
136
|
+
if (!saleNo || !lotNo) {
|
|
137
|
+
throw new Error('gsa_auction_lot requires "sale_no" (SaleNo) and "lot_no" (LotNo).');
|
|
138
|
+
}
|
|
139
|
+
const all = await fetchAuctions(apiKey);
|
|
140
|
+
const lot = all.find(
|
|
141
|
+
(a) => String(a.SaleNo ?? '') === saleNo && String(a.LotNo ?? '') === lotNo,
|
|
142
|
+
);
|
|
143
|
+
if (!lot) {
|
|
144
|
+
return { error: `No auction lot found for SaleNo "${saleNo}" LotNo "${lotNo}". It may have closed or the identifiers may be wrong.`, sale_no: saleNo, lot_no: lotNo };
|
|
145
|
+
}
|
|
146
|
+
return shapeDetail(lot);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function closingSoon(args: Record<string, unknown>, apiKey: string): Promise<unknown> {
|
|
150
|
+
const all = await fetchAuctions(apiKey);
|
|
151
|
+
const limit = clampLimit(args.limit, 25);
|
|
152
|
+
const active = all.filter((a) => {
|
|
153
|
+
const s = (a.AuctionStatus ?? '').toLowerCase();
|
|
154
|
+
// treat anything not explicitly closed/awarded/sold as active
|
|
155
|
+
return !/(closed|awarded|sold|ended|cancel)/.test(s);
|
|
156
|
+
});
|
|
157
|
+
const sorted = active
|
|
158
|
+
.map((a) => ({ a, ts: parseDate(a.AucEndDt) }))
|
|
159
|
+
.filter((x) => x.ts !== null)
|
|
160
|
+
.sort((x, y) => (x.ts as number) - (y.ts as number))
|
|
161
|
+
.map((x) => x.a);
|
|
162
|
+
const results = sorted.slice(0, limit).map(shapeSummary);
|
|
163
|
+
return { total_active: active.length, returned: results.length, lots: results };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface Auction {
|
|
167
|
+
SaleNo?: string;
|
|
168
|
+
LotNo?: number | string;
|
|
169
|
+
AucStartDt?: string;
|
|
170
|
+
AucEndDt?: string;
|
|
171
|
+
ItemName?: string;
|
|
172
|
+
PropertyAddr1?: string;
|
|
173
|
+
PropertyAddr2?: string;
|
|
174
|
+
PropertyAddr3?: string;
|
|
175
|
+
PropertyCity?: string;
|
|
176
|
+
PropertyState?: string;
|
|
177
|
+
PropertyZip?: string;
|
|
178
|
+
AuctionStatus?: string;
|
|
179
|
+
SaleLocation?: string;
|
|
180
|
+
LocationOrg?: string;
|
|
181
|
+
LocationStAddr?: string;
|
|
182
|
+
LocationCity?: string;
|
|
183
|
+
LocationST?: string;
|
|
184
|
+
LocationZip?: string;
|
|
185
|
+
BiddersCount?: number;
|
|
186
|
+
LotInfo?: { LotSequence?: number; LotDescript?: string }[];
|
|
187
|
+
Instruction1?: string;
|
|
188
|
+
Instruction2?: string;
|
|
189
|
+
Instruction3?: string;
|
|
190
|
+
ContractOfficer?: string;
|
|
191
|
+
COEmail?: string;
|
|
192
|
+
COPhone?: string;
|
|
193
|
+
Reserve?: number;
|
|
194
|
+
AucIncrement?: number;
|
|
195
|
+
HighBidAmount?: string;
|
|
196
|
+
InactivityTime?: number;
|
|
197
|
+
AgencyCode?: string;
|
|
198
|
+
BureauCode?: string;
|
|
199
|
+
AgencyName?: string;
|
|
200
|
+
BureauName?: string;
|
|
201
|
+
ItemDescURL?: string;
|
|
202
|
+
ImageURL?: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function shapeSummary(a: Auction): Record<string, unknown> {
|
|
206
|
+
return {
|
|
207
|
+
sale_no: a.SaleNo,
|
|
208
|
+
lot_no: a.LotNo,
|
|
209
|
+
item_name: a.ItemName,
|
|
210
|
+
property_location: joinLoc([a.PropertyCity, a.PropertyState, a.PropertyZip]),
|
|
211
|
+
sale_location: a.SaleLocation ?? joinLoc([a.LocationCity, a.LocationST, a.LocationZip]),
|
|
212
|
+
start_date: a.AucStartDt,
|
|
213
|
+
end_date: a.AucEndDt,
|
|
214
|
+
status: a.AuctionStatus,
|
|
215
|
+
high_bid: a.HighBidAmount,
|
|
216
|
+
reserve: a.Reserve,
|
|
217
|
+
bidders_count: a.BiddersCount,
|
|
218
|
+
agency: a.AgencyName,
|
|
219
|
+
item_url: a.ItemDescURL,
|
|
220
|
+
image_url: a.ImageURL,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function shapeDetail(a: Auction): Record<string, unknown> {
|
|
225
|
+
return {
|
|
226
|
+
sale_no: a.SaleNo,
|
|
227
|
+
lot_no: a.LotNo,
|
|
228
|
+
item_name: a.ItemName,
|
|
229
|
+
lot_info: (a.LotInfo ?? []).map((l) => ({ sequence: l.LotSequence, description: l.LotDescript })),
|
|
230
|
+
start_date: a.AucStartDt,
|
|
231
|
+
end_date: a.AucEndDt,
|
|
232
|
+
status: a.AuctionStatus,
|
|
233
|
+
high_bid: a.HighBidAmount,
|
|
234
|
+
reserve: a.Reserve,
|
|
235
|
+
bid_increment: a.AucIncrement,
|
|
236
|
+
bidders_count: a.BiddersCount,
|
|
237
|
+
inactivity_time_min: a.InactivityTime,
|
|
238
|
+
property_location: {
|
|
239
|
+
address1: a.PropertyAddr1,
|
|
240
|
+
address2: a.PropertyAddr2,
|
|
241
|
+
address3: a.PropertyAddr3,
|
|
242
|
+
city: a.PropertyCity,
|
|
243
|
+
state: a.PropertyState,
|
|
244
|
+
zip: a.PropertyZip,
|
|
245
|
+
},
|
|
246
|
+
sale_location: {
|
|
247
|
+
name: a.SaleLocation,
|
|
248
|
+
org: a.LocationOrg,
|
|
249
|
+
street: a.LocationStAddr,
|
|
250
|
+
city: a.LocationCity,
|
|
251
|
+
state: a.LocationST,
|
|
252
|
+
zip: a.LocationZip,
|
|
253
|
+
},
|
|
254
|
+
inspection_instructions: [a.Instruction1, a.Instruction2, a.Instruction3].filter(Boolean),
|
|
255
|
+
contracting_officer: { name: a.ContractOfficer, email: a.COEmail, phone: a.COPhone },
|
|
256
|
+
agency: { code: a.AgencyCode, name: a.AgencyName, bureau_code: a.BureauCode, bureau_name: a.BureauName },
|
|
257
|
+
item_url: a.ItemDescURL,
|
|
258
|
+
image_url: a.ImageURL,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function joinLoc(parts: (string | undefined)[]): string | undefined {
|
|
263
|
+
const clean = parts.map((p) => (p ?? '').trim()).filter(Boolean);
|
|
264
|
+
return clean.length ? clean.join(', ') : undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Parse GSA date strings (commonly MM/DD/YYYY or ISO) to a comparable timestamp.
|
|
268
|
+
function parseDate(v: string | undefined): number | null {
|
|
269
|
+
if (!v) return null;
|
|
270
|
+
const t = Date.parse(v);
|
|
271
|
+
if (!Number.isNaN(t)) return t;
|
|
272
|
+
const m = v.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})/);
|
|
273
|
+
if (m) {
|
|
274
|
+
const d = Date.parse(`${m[3]}-${m[1].padStart(2, '0')}-${m[2].padStart(2, '0')}`);
|
|
275
|
+
if (!Number.isNaN(d)) return d;
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function fetchAuctions(apiKey: string): Promise<Auction[]> {
|
|
281
|
+
const res = await fetch(`${BASE}?api_key=${encodeURIComponent(apiKey)}`, {
|
|
282
|
+
headers: { Accept: 'application/json', 'User-Agent': UA, 'X-Api-Key': apiKey },
|
|
283
|
+
});
|
|
284
|
+
if (!res.ok) {
|
|
285
|
+
const body = await res.text().then((t) => t.slice(0, 200)).catch(() => '');
|
|
286
|
+
throw new Error(`GSA Auctions API: ${res.status} ${body}`.trim());
|
|
287
|
+
}
|
|
288
|
+
const data = (await res.json()) as { results?: Auction[] } | Auction[];
|
|
289
|
+
const list = Array.isArray(data) ? data : data?.results ?? [];
|
|
290
|
+
return Array.isArray(list) ? list : [];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function strArg(v: unknown): string | undefined {
|
|
294
|
+
if (typeof v === 'string') {
|
|
295
|
+
const t = v.trim();
|
|
296
|
+
return t ? t : undefined;
|
|
297
|
+
}
|
|
298
|
+
if (typeof v === 'number' && Number.isFinite(v)) return String(v);
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function clampLimit(v: unknown, def: number): number {
|
|
303
|
+
const n = typeof v === 'number' ? v : typeof v === 'string' ? parseInt(v, 10) : NaN;
|
|
304
|
+
if (!Number.isFinite(n)) return def;
|
|
305
|
+
return Math.max(1, Math.min(200, Math.floor(n)));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export default { tools, callTool, meter: { credits: 1 } } satisfies McpToolExport;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|