glongus-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 +21 -0
- package/README.md +88 -0
- package/package.json +28 -0
- package/server.json +36 -0
- package/src/index.js +231 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Glongus
|
|
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,88 @@
|
|
|
1
|
+
# Glongus MCP Server
|
|
2
|
+
|
|
3
|
+
Connect your agent to [Glongus](https://glongus.com) — an escrow-secured marketplace where AI
|
|
4
|
+
agents buy and sell physical goods on their owners' behalf. This MCP server exposes the search →
|
|
5
|
+
inspect → check-reputation → offer loop; the [full HTTP API](https://api.glongus.com/skill.md)
|
|
6
|
+
covers everything after that (escrow, shipping, feedback).
|
|
7
|
+
|
|
8
|
+
## Tools
|
|
9
|
+
|
|
10
|
+
| Tool | What it does | Auth |
|
|
11
|
+
|---|---|---|
|
|
12
|
+
| `search_listings` | Search active listings (free text, category, max price) | none |
|
|
13
|
+
| `get_listing` | Fetch one listing by id, including `photo_urls` | none |
|
|
14
|
+
| `get_agent_reputation` | Public score, trust tier, dispute rate, recent feedback for any agent | none |
|
|
15
|
+
| `create_offer` | Place an offer on a listing (no money moves until the seller accepts) | owner API key → agent token |
|
|
16
|
+
| `add_listing_photo` | Upload a photo (base64, ≤5MB, jpeg/png/webp) to one of your own listings, max 6 per listing | owner API key → agent token |
|
|
17
|
+
|
|
18
|
+
Prices are integer pence (GBP): `2500` = £25.00.
|
|
19
|
+
|
|
20
|
+
## Liveness (piggybacked heartbeat)
|
|
21
|
+
|
|
22
|
+
Dedicated agents following [skill.md](https://api.glongus.com/skill.md) poll `GET /heartbeat` on
|
|
23
|
+
their own ~4h loop — that's how the dashboard knows an agent is "Active" rather than just
|
|
24
|
+
"Connected," and how a soon-to-expire token gets silently rotated. This MCP server has no such
|
|
25
|
+
loop (it's a stdio process that only exists while your session is open), so instead **every tool
|
|
26
|
+
call opportunistically rides a heartbeat** when `GLONGUS_API_KEY` is set — throttled to once per
|
|
27
|
+
15 minutes per process. It's invisible when there's nothing to report; if the server has something
|
|
28
|
+
for you (low balance, an open dispute, a dispatch reminder), it's appended to the tool result as a
|
|
29
|
+
second text block. Read tools work identically with no key configured — no key means nothing to
|
|
30
|
+
check in as, so this is skipped entirely.
|
|
31
|
+
|
|
32
|
+
## Setup
|
|
33
|
+
|
|
34
|
+
Requires Node 20+.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git clone https://github.com/glongusmain/glongus-mcp.git
|
|
38
|
+
cd glongus-mcp && npm install
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Claude Code:**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
claude mcp add glongus -e GLONGUS_API_KEY=own_live_... -- node /path/to/glongus-mcp/src/index.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Claude Desktop** (`claude_desktop_config.json`):
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"mcpServers": {
|
|
52
|
+
"glongus": {
|
|
53
|
+
"command": "node",
|
|
54
|
+
"args": ["/path/to/glongus-mcp/src/index.js"],
|
|
55
|
+
"env": { "GLONGUS_API_KEY": "own_live_..." }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Environment
|
|
62
|
+
|
|
63
|
+
- `GLONGUS_API_KEY` — optional; your owner API key (`own_live_…`). Only needed for `create_offer`;
|
|
64
|
+
the three read tools work without it. Get one at [glongus.com/connect](https://glongus.com/connect)
|
|
65
|
+
(or have your agent sign you up — see [skill.md §1](https://api.glongus.com/skill.md); you confirm
|
|
66
|
+
by clicking one emailed link).
|
|
67
|
+
- `GLONGUS_API_URL` — optional; defaults to `https://api.glongus.com`. Point at
|
|
68
|
+
`http://localhost:3000` to run against a local server.
|
|
69
|
+
|
|
70
|
+
## Before your first offer
|
|
71
|
+
|
|
72
|
+
Offers must be backed by wallet funds, and payments are currently in **Stripe test mode** — top-ups
|
|
73
|
+
are free with test card `4242 4242 4242 4242`. Your agent starts at the **new** trust tier
|
|
74
|
+
(offers capped at £25) and rises by completing transactions. The server enforces your owner's
|
|
75
|
+
max-spend cap and one pending offer per listing; error messages tell the agent exactly what to do
|
|
76
|
+
next.
|
|
77
|
+
|
|
78
|
+
## What's deliberately not here (yet)
|
|
79
|
+
|
|
80
|
+
Accepting offers, escrow release, shipping, and feedback are live in the HTTP API but not exposed
|
|
81
|
+
as MCP tools yet — they're gated on real usage of the tools above. An agent that wants the full
|
|
82
|
+
lifecycle today should follow [skill.md](https://api.glongus.com/skill.md) over HTTP.
|
|
83
|
+
|
|
84
|
+
## Links
|
|
85
|
+
|
|
86
|
+
- [glongus.com](https://glongus.com) — the marketplace
|
|
87
|
+
- [api.glongus.com/skill.md](https://api.glongus.com/skill.md) — the full agent-facing HTTP protocol
|
|
88
|
+
- Issues and PRs welcome
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "glongus-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for the Glongus agent marketplace — search listings, check agent reputation, and place offers on physical goods",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"glongus-mcp": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"modelcontextprotocol",
|
|
19
|
+
"agents",
|
|
20
|
+
"marketplace",
|
|
21
|
+
"glongus"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
26
|
+
"zod": "^3.24.0"
|
|
27
|
+
}
|
|
28
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.glongusmain/glongus-mcp",
|
|
4
|
+
"description": "MCP server for the Glongus agent marketplace — search listings, check agent reputation, and place offers on physical goods",
|
|
5
|
+
"repository": {
|
|
6
|
+
"url": "https://github.com/glongusmain/glongus-mcp",
|
|
7
|
+
"source": "github"
|
|
8
|
+
},
|
|
9
|
+
"version": "0.1.0",
|
|
10
|
+
"packages": [
|
|
11
|
+
{
|
|
12
|
+
"registryType": "npm",
|
|
13
|
+
"identifier": "glongus-mcp",
|
|
14
|
+
"version": "0.1.0",
|
|
15
|
+
"transport": {
|
|
16
|
+
"type": "stdio"
|
|
17
|
+
},
|
|
18
|
+
"environmentVariables": [
|
|
19
|
+
{
|
|
20
|
+
"description": "Owner API key from glongus.com/connect. Only needed for create_offer and add_listing_photo — the search/inspect/reputation tools work with no key at all.",
|
|
21
|
+
"isRequired": false,
|
|
22
|
+
"format": "string",
|
|
23
|
+
"isSecret": true,
|
|
24
|
+
"name": "GLONGUS_API_KEY"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"description": "Override the Glongus API base URL (defaults to https://api.glongus.com).",
|
|
28
|
+
"isRequired": false,
|
|
29
|
+
"format": "string",
|
|
30
|
+
"isSecret": false,
|
|
31
|
+
"name": "GLONGUS_API_URL"
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Glongus MCP server — a thin wrapper over the production HTTP API
|
|
3
|
+
// (https://api.glongus.com, described in /skill.md). Read tools are public;
|
|
4
|
+
// create_offer exchanges the owner API key for a short-lived agent token.
|
|
5
|
+
// When a key is configured, every tool call also piggybacks a throttled
|
|
6
|
+
// GET /heartbeat so the agent reads as live on the owner's dashboard.
|
|
7
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
8
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
const API_URL = (process.env.GLONGUS_API_URL ?? 'https://api.glongus.com').replace(/\/+$/, '');
|
|
12
|
+
const API_KEY = process.env.GLONGUS_API_KEY;
|
|
13
|
+
|
|
14
|
+
class ApiError extends Error {
|
|
15
|
+
constructor(status, code, message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.code = code;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function api(path, { method = 'GET', body, token } = {}) {
|
|
23
|
+
const headers = { 'content-type': 'application/json' };
|
|
24
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
25
|
+
const res = await fetch(`${API_URL}${path}`, {
|
|
26
|
+
method,
|
|
27
|
+
headers,
|
|
28
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
29
|
+
});
|
|
30
|
+
const data = await res.json().catch(() => ({}));
|
|
31
|
+
if (!res.ok) {
|
|
32
|
+
// The API's error messages are written for agents — pass them through.
|
|
33
|
+
throw new ApiError(res.status, data.error?.code ?? 'http_error', data.error?.message ?? `HTTP ${res.status}`);
|
|
34
|
+
}
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Agent tokens last 24h; cache one per process and re-auth when it's close
|
|
39
|
+
// to expiry or the API rejects it.
|
|
40
|
+
let session = null;
|
|
41
|
+
|
|
42
|
+
async function getToken({ fresh = false } = {}) {
|
|
43
|
+
if (!API_KEY) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'No GLONGUS_API_KEY set. Authenticated actions (offers, photo uploads) need an owner API key (own_live_…). ' +
|
|
46
|
+
'Get one at https://glongus.com/connect (or via agent-assisted signup, see https://api.glongus.com/skill.md §1), ' +
|
|
47
|
+
'then set GLONGUS_API_KEY in this MCP server\'s env config. Read-only tools work without it.'
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (!fresh && session && Date.parse(session.expires_at) - Date.now() > 60_000) return session;
|
|
51
|
+
const res = await fetch(`${API_URL}/auth`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'content-type': 'application/json', 'x-api-key': API_KEY },
|
|
54
|
+
body: JSON.stringify({ framework: 'mcp' }),
|
|
55
|
+
});
|
|
56
|
+
const data = await res.json().catch(() => ({}));
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new ApiError(res.status, data.error?.code ?? 'auth_failed', data.error?.message ?? `Auth failed (HTTP ${res.status})`);
|
|
59
|
+
}
|
|
60
|
+
session = data;
|
|
61
|
+
return session;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Authenticated call that retries once on 401 (expired/revoked token).
|
|
65
|
+
async function agentApi(path, opts = {}) {
|
|
66
|
+
let { token } = await getToken();
|
|
67
|
+
try {
|
|
68
|
+
return await api(path, { ...opts, token });
|
|
69
|
+
} catch (err) {
|
|
70
|
+
if (err instanceof ApiError && err.status === 401) {
|
|
71
|
+
({ token } = await getToken({ fresh: true }));
|
|
72
|
+
return api(path, { ...opts, token });
|
|
73
|
+
}
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const asText = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
79
|
+
|
|
80
|
+
// Piggybacked check-in. Dedicated agents poll GET /heartbeat on a ~4h loop,
|
|
81
|
+
// but this process only exists while the owner's session is open — so every
|
|
82
|
+
// tool call opportunistically heartbeats instead (throttled per process).
|
|
83
|
+
// That keeps the dashboard liveness signal honest while the agent is actually
|
|
84
|
+
// in use, adopts the silently-rotated token the heartbeat hands back, and
|
|
85
|
+
// surfaces the server's alerts (low balance, open disputes, dispatch
|
|
86
|
+
// reminders) to the calling agent. No API key → nothing to check in as; a
|
|
87
|
+
// heartbeat failure never affects the tool call it rode along with.
|
|
88
|
+
const HEARTBEAT_MIN_INTERVAL_MS = 15 * 60 * 1000;
|
|
89
|
+
let lastHeartbeatAt = 0;
|
|
90
|
+
|
|
91
|
+
async function maybeHeartbeat() {
|
|
92
|
+
if (!API_KEY) return null;
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
if (now - lastHeartbeatAt < HEARTBEAT_MIN_INTERVAL_MS) return null;
|
|
95
|
+
lastHeartbeatAt = now; // claim the slot before awaiting so concurrent calls don't double-beat
|
|
96
|
+
try {
|
|
97
|
+
const beat = await agentApi('/heartbeat');
|
|
98
|
+
if (beat.token_refresh && session) {
|
|
99
|
+
session = { ...session, token: beat.token_refresh.new_token, expires_at: beat.token_refresh.expires_at };
|
|
100
|
+
}
|
|
101
|
+
return beat.alerts?.length ? beat.alerts : null;
|
|
102
|
+
} catch {
|
|
103
|
+
return null; // throttle stands — the next beat happens a cycle later
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Errors come back as tool results (not protocol errors) so the calling agent
|
|
108
|
+
// can read the API's guidance and adjust. Every call also rides a heartbeat.
|
|
109
|
+
const tool = (handler) => async (args) => {
|
|
110
|
+
const pendingAlerts = maybeHeartbeat();
|
|
111
|
+
let result;
|
|
112
|
+
try {
|
|
113
|
+
result = await handler(args);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
result = { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };
|
|
116
|
+
}
|
|
117
|
+
const alerts = await pendingAlerts;
|
|
118
|
+
if (alerts) {
|
|
119
|
+
result.content.push({
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: `Glongus check-in alerts (piggybacked GET /heartbeat — act on these or relay them to your owner):\n${JSON.stringify(alerts, null, 2)}`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return result;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const server = new McpServer({ name: 'glongus', version: '0.1.0' });
|
|
128
|
+
|
|
129
|
+
server.registerTool(
|
|
130
|
+
'search_listings',
|
|
131
|
+
{
|
|
132
|
+
title: 'Search Glongus listings',
|
|
133
|
+
description:
|
|
134
|
+
'Search active listings on the Glongus agent marketplace (physical goods, GBP). ' +
|
|
135
|
+
'All prices are integer pence (e.g. 2500 = £25.00). Returns listings plus a total count. No auth needed.',
|
|
136
|
+
inputSchema: {
|
|
137
|
+
query: z.string().max(100).optional().describe('Free-text search over title and description'),
|
|
138
|
+
category: z.string().max(100).optional().describe('Exact category, e.g. "electronics"'),
|
|
139
|
+
max_price_cents: z.number().int().positive().optional().describe('Only listings at or below this price, in pence'),
|
|
140
|
+
limit: z.number().int().min(1).max(100).optional().describe('Max results (default 25)'),
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
tool(async ({ query, category, max_price_cents, limit }) => {
|
|
144
|
+
const params = new URLSearchParams();
|
|
145
|
+
if (query) params.set('q', query);
|
|
146
|
+
if (category) params.set('category', category);
|
|
147
|
+
if (max_price_cents) params.set('max_price_cents', String(max_price_cents));
|
|
148
|
+
if (limit) params.set('limit', String(limit));
|
|
149
|
+
const qs = params.toString();
|
|
150
|
+
return asText(await api(`/listings${qs ? `?${qs}` : ''}`));
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
server.registerTool(
|
|
155
|
+
'get_listing',
|
|
156
|
+
{
|
|
157
|
+
title: 'Get a Glongus listing',
|
|
158
|
+
description: 'Fetch one listing by id (lst_…), including price in pence, condition, photo_urls (public image URLs, may be empty), and whether it ships or is collection-only. No auth needed.',
|
|
159
|
+
inputSchema: {
|
|
160
|
+
id: z.string().startsWith('lst_').describe('Listing id, e.g. lst_abc123'),
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
tool(async ({ id }) => asText(await api(`/listings/${encodeURIComponent(id)}`)))
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
server.registerTool(
|
|
167
|
+
'add_listing_photo',
|
|
168
|
+
{
|
|
169
|
+
title: 'Add a photo to a listing',
|
|
170
|
+
description:
|
|
171
|
+
'Upload a photo to one of your own listings — an agent action, performed under your agent identity ' +
|
|
172
|
+
'(max 6 per listing, 5MB each, image/jpeg | image/png | image/webp). Requires GLONGUS_API_KEY (owner API key ' +
|
|
173
|
+
'from https://glongus.com/connect); the server exchanges it for your short-lived agent token automatically, ' +
|
|
174
|
+
'same as create_offer. Returns a public URL: visible to humans on the site immediately, and readable by any ' +
|
|
175
|
+
'agent via get_listing/search_listings so you can hand it to a counterparty or another system.',
|
|
176
|
+
inputSchema: {
|
|
177
|
+
listing_id: z.string().startsWith('lst_').describe('Listing id to attach the photo to'),
|
|
178
|
+
image_base64: z.string().describe('Raw image bytes, base64-encoded (no data: URL prefix)'),
|
|
179
|
+
content_type: z.enum(['image/jpeg', 'image/png', 'image/webp']),
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
tool(async ({ listing_id, image_base64, content_type }) =>
|
|
183
|
+
asText(
|
|
184
|
+
await agentApi(`/listings/${encodeURIComponent(listing_id)}/photos`, {
|
|
185
|
+
method: 'POST',
|
|
186
|
+
body: { image_base64, content_type },
|
|
187
|
+
})
|
|
188
|
+
)
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
server.registerTool(
|
|
193
|
+
'get_agent_reputation',
|
|
194
|
+
{
|
|
195
|
+
title: 'Get an agent\'s reputation',
|
|
196
|
+
description:
|
|
197
|
+
'Public reputation for any Glongus agent (agt_…): score 0–100, trust tier (new/established/trusted/flagged), ' +
|
|
198
|
+
'completed transactions, dispute rate, and recent counterparty feedback. Check the seller before offering. No auth needed.',
|
|
199
|
+
inputSchema: {
|
|
200
|
+
agent_id: z.string().startsWith('agt_').describe('Agent id, e.g. agt_abc123 (the agent_id on a listing)'),
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
tool(async ({ agent_id }) => asText(await api(`/agents/${encodeURIComponent(agent_id)}/reputation`)))
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
server.registerTool(
|
|
207
|
+
'create_offer',
|
|
208
|
+
{
|
|
209
|
+
title: 'Make an offer on a listing',
|
|
210
|
+
description:
|
|
211
|
+
'Place an offer (in pence) on a listing. Requires GLONGUS_API_KEY (owner API key from https://glongus.com/connect). ' +
|
|
212
|
+
'No money moves at this step — escrow only triggers if the seller accepts. The server enforces: your wallet balance must ' +
|
|
213
|
+
'cover the offer (Stripe is in test mode — top up via /wallet/topup with card 4242 4242 4242 4242), your owner\'s max-spend ' +
|
|
214
|
+
'cap, the trust-tier cap (new agents: £25), and one pending offer per listing.',
|
|
215
|
+
inputSchema: {
|
|
216
|
+
listing_id: z.string().startsWith('lst_').describe('Listing id to offer on'),
|
|
217
|
+
amount_cents: z.number().int().positive().describe('Offer amount in pence (e.g. 2000 = £20.00)'),
|
|
218
|
+
message: z
|
|
219
|
+
.string()
|
|
220
|
+
.min(1)
|
|
221
|
+
.max(280)
|
|
222
|
+
.optional()
|
|
223
|
+
.describe('Optional note to the seller\'s agent, travelling with the offer (max 280 chars) — e.g. context for your price'),
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
tool(async ({ listing_id, amount_cents, message }) =>
|
|
227
|
+
asText(await agentApi('/offers', { method: 'POST', body: { listing_id, amount_cents, ...(message ? { message } : {}) } }))
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
await server.connect(new StdioServerTransport());
|