rendezvous-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 +124 -0
- package/build/index.d.ts +2 -0
- package/build/index.js +109 -0
- package/build/index.js.map +1 -0
- package/build/l402.d.ts +21 -0
- package/build/l402.js +22 -0
- package/build/l402.js.map +1 -0
- package/build/routing.d.ts +25 -0
- package/build/routing.js +99 -0
- package/build/routing.js.map +1 -0
- package/build/tools/directions.d.ts +27 -0
- package/build/tools/directions.js +69 -0
- package/build/tools/directions.js.map +1 -0
- package/build/tools/isochrone.d.ts +22 -0
- package/build/tools/isochrone.js +60 -0
- package/build/tools/isochrone.js.map +1 -0
- package/build/tools/score-venues.d.ts +31 -0
- package/build/tools/score-venues.js +124 -0
- package/build/tools/score-venues.js.map +1 -0
- package/build/tools/search-venues.d.ts +20 -0
- package/build/tools/search-venues.js +63 -0
- package/build/tools/search-venues.js.map +1 -0
- package/build/tools/store-credentials.d.ts +12 -0
- package/build/tools/store-credentials.js +35 -0
- package/build/tools/store-credentials.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 forgesworn
|
|
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,124 @@
|
|
|
1
|
+
# rendezvous-mcp
|
|
2
|
+
|
|
3
|
+
**Fair meeting points for AI — isochrone-based fairness, not naive midpoints.**
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/rendezvous-mcp)
|
|
6
|
+
[](https://github.com/forgesworn/rendezvous-mcp/blob/main/LICENSE)
|
|
7
|
+

|
|
8
|
+
[](https://primal.net/p/npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2)
|
|
9
|
+
|
|
10
|
+
MCP server for AI-driven meeting point discovery. Give your AI the ability to answer **"where should we meet?"** using real travel times, venue availability, and fairness algorithms.
|
|
11
|
+
|
|
12
|
+
Works out of the box — free public routing, no API keys needed. Self-host Valhalla for unlimited queries, or use L402 Lightning credits for our hosted endpoint.
|
|
13
|
+
|
|
14
|
+
## Tools
|
|
15
|
+
|
|
16
|
+
| Tool | Description |
|
|
17
|
+
|------|-------------|
|
|
18
|
+
| `score_venues` | Score candidate venues by travel time fairness for 2–10 participants |
|
|
19
|
+
| `search_venues` | Search for venues near a location using OpenStreetMap |
|
|
20
|
+
| `get_isochrone` | Get a reachability polygon (everywhere reachable within N minutes) |
|
|
21
|
+
| `get_directions` | Get directions between two points with turn-by-turn steps |
|
|
22
|
+
| `store_routing_credentials` | Store L402 macaroon + preimage after Lightning payment |
|
|
23
|
+
|
|
24
|
+
## Quick start
|
|
25
|
+
|
|
26
|
+
Add to your MCP client config (Claude Code, Claude Desktop, Cursor, etc.):
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"rendezvous": {
|
|
32
|
+
"command": "npx",
|
|
33
|
+
"args": ["rendezvous-mcp"]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then ask your AI: *"Where's a fair place for Alice in London, Bob in Bristol, and Carol in Birmingham to meet for lunch?"*
|
|
40
|
+
|
|
41
|
+
## Remote (HTTP/SSE)
|
|
42
|
+
|
|
43
|
+
For ChatGPT, remote AI agents, or any client that connects over HTTP:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
TRANSPORT=http npx rendezvous-mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Starts a Streamable HTTP server on port 3002 with the MCP endpoint at `/mcp`.
|
|
50
|
+
|
|
51
|
+
### ChatGPT connector
|
|
52
|
+
|
|
53
|
+
In ChatGPT settings, add an MCP server with:
|
|
54
|
+
- **URL:** `http://your-host:3002/mcp`
|
|
55
|
+
- **Transport:** Streamable HTTP
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
| Variable | Default | Description |
|
|
60
|
+
|----------|---------|-------------|
|
|
61
|
+
| `TRANSPORT` | `stdio` | Transport mode: `stdio` or `http` |
|
|
62
|
+
| `PORT` | `3002` | HTTP server port (HTTP mode only) |
|
|
63
|
+
| `HOST` | `0.0.0.0` | HTTP bind address (HTTP mode only) |
|
|
64
|
+
| `VALHALLA_URL` | `https://routing.trotters.cc` | Routing engine URL |
|
|
65
|
+
| `OVERPASS_URL` | Public endpoints | Venue search API |
|
|
66
|
+
|
|
67
|
+
## Self-hosted routing
|
|
68
|
+
|
|
69
|
+
For unlimited queries with no rate limits, run your own Valhalla instance:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"rendezvous": {
|
|
75
|
+
"command": "npx",
|
|
76
|
+
"args": ["rendezvous-mcp"],
|
|
77
|
+
"env": {
|
|
78
|
+
"VALHALLA_URL": "http://localhost:8002"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## How it works
|
|
86
|
+
|
|
87
|
+
1. User asks "Where should we meet?"
|
|
88
|
+
2. AI geocodes participant locations
|
|
89
|
+
3. AI calls `search_venues` to find candidate venues near the area
|
|
90
|
+
4. AI calls `score_venues` with participants + candidates — returns ranked results with travel times and fairness scores
|
|
91
|
+
5. AI presents the fairest option with travel times for each person
|
|
92
|
+
|
|
93
|
+
For deeper analysis, the AI can use `get_isochrone` to visualise reachability and `get_directions` for turn-by-turn navigation.
|
|
94
|
+
|
|
95
|
+
## L402 payments
|
|
96
|
+
|
|
97
|
+
The default routing endpoint (`routing.trotters.cc`) offers free requests. When the free tier is exhausted, tools return a `payment_required` response with a Lightning invoice. After payment, call `store_routing_credentials` to store the macaroon for the session.
|
|
98
|
+
|
|
99
|
+
Self-hosted Valhalla has no payment requirement.
|
|
100
|
+
|
|
101
|
+
## Architecture
|
|
102
|
+
|
|
103
|
+
Thin MCP wrapper over [rendezvous-kit](https://github.com/forgesworn/rendezvous-kit) — the open-source TypeScript library for isochrone intersection, venue search, and fairness scoring. Each tool is an extracted handler function (testable without MCP) plus a registration one-liner.
|
|
104
|
+
|
|
105
|
+
## Development
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm install
|
|
109
|
+
npm run build
|
|
110
|
+
npm test
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Licence
|
|
114
|
+
|
|
115
|
+
[MIT](https://github.com/forgesworn/rendezvous-mcp/blob/main/LICENSE)
|
|
116
|
+
|
|
117
|
+
## Support
|
|
118
|
+
|
|
119
|
+
For issues and feature requests, see [GitHub Issues](https://github.com/forgesworn/rendezvous-mcp/issues).
|
|
120
|
+
|
|
121
|
+
If you find rendezvous-mcp useful, consider sending a tip:
|
|
122
|
+
|
|
123
|
+
- **Lightning:** `thedonkey@strike.me`
|
|
124
|
+
- **Nostr zaps:** `npub1mgvlrnf5hm9yf0n5mf9nqmvarhvxkc6remu5ec3vf8r0txqkuk7su0e7q2`
|
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
4
|
+
import { RoutingClient, validateUrl } from './routing.js';
|
|
5
|
+
import { registerScoreVenuesTool } from './tools/score-venues.js';
|
|
6
|
+
import { registerSearchVenuesTool } from './tools/search-venues.js';
|
|
7
|
+
import { registerIsochroneTool } from './tools/isochrone.js';
|
|
8
|
+
import { registerDirectionsTool } from './tools/directions.js';
|
|
9
|
+
import { registerStoreCredentialsTool } from './tools/store-credentials.js';
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const { version } = require('../package.json');
|
|
12
|
+
const TRANSPORT = process.env.TRANSPORT ?? 'stdio';
|
|
13
|
+
const PORT = Math.max(1, Math.min(65535, parseInt(process.env.PORT ?? '3002', 10) || 3002));
|
|
14
|
+
const HOST = process.env.HOST ?? '0.0.0.0';
|
|
15
|
+
const VALHALLA_URL = process.env.VALHALLA_URL;
|
|
16
|
+
const OVERPASS_URL = process.env.OVERPASS_URL
|
|
17
|
+
? validateUrl(process.env.OVERPASS_URL, 'OVERPASS_URL')
|
|
18
|
+
: undefined;
|
|
19
|
+
const CORS_ORIGIN = process.env.CORS_ORIGIN ?? '*';
|
|
20
|
+
const RATE_LIMIT_MAX = Math.max(1, parseInt(process.env.RATE_LIMIT_MAX ?? '60', 10) || 60);
|
|
21
|
+
const RATE_LIMIT_WINDOW = 60_000;
|
|
22
|
+
const server = new McpServer({
|
|
23
|
+
name: 'rendezvous-mcp',
|
|
24
|
+
version,
|
|
25
|
+
});
|
|
26
|
+
const routingClient = new RoutingClient({
|
|
27
|
+
valhallaUrl: VALHALLA_URL,
|
|
28
|
+
});
|
|
29
|
+
// Register all tools
|
|
30
|
+
registerScoreVenuesTool(server, routingClient);
|
|
31
|
+
registerSearchVenuesTool(server, OVERPASS_URL);
|
|
32
|
+
registerIsochroneTool(server, routingClient);
|
|
33
|
+
registerDirectionsTool(server, routingClient);
|
|
34
|
+
registerStoreCredentialsTool(server, routingClient);
|
|
35
|
+
if (TRANSPORT === 'http') {
|
|
36
|
+
const { default: express } = await import('express');
|
|
37
|
+
const { default: cors } = await import('cors');
|
|
38
|
+
const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
39
|
+
const app = express();
|
|
40
|
+
app.use(cors({ origin: CORS_ORIGIN }));
|
|
41
|
+
app.use(express.json({ limit: '100kb' }));
|
|
42
|
+
// Simple in-memory rate limiter
|
|
43
|
+
const rateLimitMap = new Map();
|
|
44
|
+
const cleanupInterval = setInterval(() => {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
for (const [ip, entry] of rateLimitMap) {
|
|
47
|
+
if (now > entry.resetAt)
|
|
48
|
+
rateLimitMap.delete(ip);
|
|
49
|
+
}
|
|
50
|
+
}, RATE_LIMIT_WINDOW);
|
|
51
|
+
cleanupInterval.unref();
|
|
52
|
+
app.use((req, res, next) => {
|
|
53
|
+
const ip = req.ip ?? req.socket.remoteAddress ?? 'unknown';
|
|
54
|
+
const now = Date.now();
|
|
55
|
+
const entry = rateLimitMap.get(ip);
|
|
56
|
+
if (!entry || now > entry.resetAt) {
|
|
57
|
+
rateLimitMap.set(ip, { count: 1, resetAt: now + RATE_LIMIT_WINDOW });
|
|
58
|
+
return next();
|
|
59
|
+
}
|
|
60
|
+
entry.count++;
|
|
61
|
+
if (entry.count > RATE_LIMIT_MAX) {
|
|
62
|
+
res.status(429).json({ error: 'Too many requests' });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
next();
|
|
66
|
+
});
|
|
67
|
+
app.get('/health', (_req, res) => {
|
|
68
|
+
res.json({
|
|
69
|
+
status: 'ok',
|
|
70
|
+
server: 'rendezvous-mcp',
|
|
71
|
+
version,
|
|
72
|
+
valhalla_configured: !!VALHALLA_URL,
|
|
73
|
+
tools: ['score_venues', 'search_venues', 'get_isochrone', 'get_directions', 'store_routing_credentials'],
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
const transport = new StreamableHTTPServerTransport({
|
|
77
|
+
sessionIdGenerator: undefined,
|
|
78
|
+
});
|
|
79
|
+
await server.connect(transport);
|
|
80
|
+
app.post('/mcp', async (req, res) => {
|
|
81
|
+
await transport.handleRequest(req, res, req.body);
|
|
82
|
+
});
|
|
83
|
+
app.get('/mcp', async (req, res) => {
|
|
84
|
+
await transport.handleRequest(req, res);
|
|
85
|
+
});
|
|
86
|
+
app.delete('/mcp', async (req, res) => {
|
|
87
|
+
await transport.handleRequest(req, res);
|
|
88
|
+
});
|
|
89
|
+
const httpServer = app.listen(PORT, HOST, () => {
|
|
90
|
+
console.error(`rendezvous-mcp HTTP server listening on ${HOST}:${PORT}`);
|
|
91
|
+
console.error('MCP endpoint: /mcp');
|
|
92
|
+
console.error('Health check: /health');
|
|
93
|
+
});
|
|
94
|
+
// Graceful shutdown
|
|
95
|
+
const shutdown = () => {
|
|
96
|
+
console.error('Shutting down gracefully…');
|
|
97
|
+
httpServer.close(() => process.exit(0));
|
|
98
|
+
setTimeout(() => process.exit(1), 5000).unref();
|
|
99
|
+
};
|
|
100
|
+
process.on('SIGTERM', shutdown);
|
|
101
|
+
process.on('SIGINT', shutdown);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
105
|
+
const transport = new StdioServerTransport();
|
|
106
|
+
await server.connect(transport);
|
|
107
|
+
console.error('rendezvous-mcp server running on stdio');
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/build/l402.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Payment-required response returned to MCP when Valhalla returns 402. */
|
|
2
|
+
export interface L402PaymentRequired {
|
|
3
|
+
status: 'payment_required';
|
|
4
|
+
message: string;
|
|
5
|
+
invoice: string;
|
|
6
|
+
macaroon: string;
|
|
7
|
+
payment_hash: string;
|
|
8
|
+
payment_url: string;
|
|
9
|
+
amount_sats: number;
|
|
10
|
+
}
|
|
11
|
+
/** In-memory L402 credential storage for a single MCP session. */
|
|
12
|
+
export declare class L402State {
|
|
13
|
+
private macaroon;
|
|
14
|
+
private preimage;
|
|
15
|
+
/** Store credentials after the user pays an invoice. */
|
|
16
|
+
store(macaroon: string, preimage: string): void;
|
|
17
|
+
/** Return the Authorization header value, or null if no credentials. */
|
|
18
|
+
getAuthHeader(): string | null;
|
|
19
|
+
/** Clear stored credentials. */
|
|
20
|
+
clear(): void;
|
|
21
|
+
}
|
package/build/l402.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** In-memory L402 credential storage for a single MCP session. */
|
|
2
|
+
export class L402State {
|
|
3
|
+
macaroon = null;
|
|
4
|
+
preimage = null;
|
|
5
|
+
/** Store credentials after the user pays an invoice. */
|
|
6
|
+
store(macaroon, preimage) {
|
|
7
|
+
this.macaroon = macaroon;
|
|
8
|
+
this.preimage = preimage;
|
|
9
|
+
}
|
|
10
|
+
/** Return the Authorization header value, or null if no credentials. */
|
|
11
|
+
getAuthHeader() {
|
|
12
|
+
if (!this.macaroon || !this.preimage)
|
|
13
|
+
return null;
|
|
14
|
+
return `L402 ${this.macaroon}:${this.preimage}`;
|
|
15
|
+
}
|
|
16
|
+
/** Clear stored credentials. */
|
|
17
|
+
clear() {
|
|
18
|
+
this.macaroon = null;
|
|
19
|
+
this.preimage = null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=l402.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"l402.js","sourceRoot":"","sources":["../src/l402.ts"],"names":[],"mappings":"AAWA,kEAAkE;AAClE,MAAM,OAAO,SAAS;IACZ,QAAQ,GAAkB,IAAI,CAAA;IAC9B,QAAQ,GAAkB,IAAI,CAAA;IAEtC,wDAAwD;IACxD,KAAK,CAAC,QAAgB,EAAE,QAAgB;QACtC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;QACxB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED,wEAAwE;IACxE,aAAa;QACX,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAA;QACjD,OAAO,QAAQ,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAA;IACjD,CAAC;IAED,gCAAgC;IAChC,KAAK;QACH,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;QACpB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;IACtB,CAAC;CACF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ValhallaEngine } from 'rendezvous-kit';
|
|
2
|
+
import type { LatLon, TransportMode, Isochrone, RouteMatrix, RouteGeometry } from 'rendezvous-kit';
|
|
3
|
+
import type { L402PaymentRequired } from './l402.js';
|
|
4
|
+
/** Validate that a string is an HTTP(S) URL. */
|
|
5
|
+
export declare function validateUrl(url: string, label: string): string;
|
|
6
|
+
export declare class RoutingClient {
|
|
7
|
+
readonly valhallaUrl: string;
|
|
8
|
+
private readonly l402;
|
|
9
|
+
constructor(config?: {
|
|
10
|
+
valhallaUrl?: string;
|
|
11
|
+
});
|
|
12
|
+
/** Store L402 credentials after user pays an invoice. */
|
|
13
|
+
storeL402Credentials(macaroon: string, preimage: string): void;
|
|
14
|
+
/** Build a ValhallaEngine with current auth headers. */
|
|
15
|
+
getEngine(): ValhallaEngine;
|
|
16
|
+
/** Compute isochrone, returning L402PaymentRequired on 402. */
|
|
17
|
+
computeIsochrone(origin: LatLon, mode: TransportMode, timeMinutes: number): Promise<Isochrone | L402PaymentRequired>;
|
|
18
|
+
/** Compute route matrix, returning L402PaymentRequired on 402. */
|
|
19
|
+
computeRouteMatrix(origins: LatLon[], destinations: LatLon[], mode: TransportMode): Promise<RouteMatrix | L402PaymentRequired>;
|
|
20
|
+
/** Compute route, returning L402PaymentRequired on 402. */
|
|
21
|
+
computeRoute(origin: LatLon, destination: LatLon, mode: TransportMode): Promise<RouteGeometry | L402PaymentRequired>;
|
|
22
|
+
private handleError;
|
|
23
|
+
}
|
|
24
|
+
/** Type guard for L402 payment required responses. */
|
|
25
|
+
export declare function isPaymentRequired(result: unknown): result is L402PaymentRequired;
|
package/build/routing.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ValhallaEngine, ValhallaError } from 'rendezvous-kit';
|
|
2
|
+
import { L402State } from './l402.js';
|
|
3
|
+
const DEFAULT_VALHALLA_URL = 'https://routing.trotters.cc';
|
|
4
|
+
/** Validate that a string is an HTTP(S) URL. */
|
|
5
|
+
export function validateUrl(url, label) {
|
|
6
|
+
let parsed;
|
|
7
|
+
try {
|
|
8
|
+
parsed = new URL(url);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new TypeError(`${label}: invalid URL "${url}"`);
|
|
12
|
+
}
|
|
13
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
14
|
+
throw new TypeError(`${label}: must use http or https, got "${parsed.protocol}"`);
|
|
15
|
+
}
|
|
16
|
+
return url.replace(/\/$/, '');
|
|
17
|
+
}
|
|
18
|
+
export class RoutingClient {
|
|
19
|
+
valhallaUrl;
|
|
20
|
+
l402;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.valhallaUrl = config?.valhallaUrl
|
|
23
|
+
? validateUrl(config.valhallaUrl, 'RoutingClient valhallaUrl')
|
|
24
|
+
: DEFAULT_VALHALLA_URL;
|
|
25
|
+
this.l402 = new L402State();
|
|
26
|
+
}
|
|
27
|
+
/** Store L402 credentials after user pays an invoice. */
|
|
28
|
+
storeL402Credentials(macaroon, preimage) {
|
|
29
|
+
this.l402.store(macaroon, preimage);
|
|
30
|
+
}
|
|
31
|
+
/** Build a ValhallaEngine with current auth headers. */
|
|
32
|
+
getEngine() {
|
|
33
|
+
const headers = {};
|
|
34
|
+
const auth = this.l402.getAuthHeader();
|
|
35
|
+
if (auth)
|
|
36
|
+
headers['Authorization'] = auth;
|
|
37
|
+
return new ValhallaEngine({ baseUrl: this.valhallaUrl, headers });
|
|
38
|
+
}
|
|
39
|
+
/** Compute isochrone, returning L402PaymentRequired on 402. */
|
|
40
|
+
async computeIsochrone(origin, mode, timeMinutes) {
|
|
41
|
+
try {
|
|
42
|
+
return await this.getEngine().computeIsochrone(origin, mode, timeMinutes);
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
return this.handleError(err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Compute route matrix, returning L402PaymentRequired on 402. */
|
|
49
|
+
async computeRouteMatrix(origins, destinations, mode) {
|
|
50
|
+
try {
|
|
51
|
+
return await this.getEngine().computeRouteMatrix(origins, destinations, mode);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
return this.handleError(err);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Compute route, returning L402PaymentRequired on 402. */
|
|
58
|
+
async computeRoute(origin, destination, mode) {
|
|
59
|
+
try {
|
|
60
|
+
return await this.getEngine().computeRoute(origin, destination, mode);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
return this.handleError(err);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
handleError(err) {
|
|
67
|
+
if (err instanceof ValhallaError && err.status === 402) {
|
|
68
|
+
try {
|
|
69
|
+
const body = JSON.parse(err.body);
|
|
70
|
+
return {
|
|
71
|
+
status: 'payment_required',
|
|
72
|
+
message: 'Free tier exhausted. Pay to continue using the routing service.',
|
|
73
|
+
invoice: body.invoice ?? '',
|
|
74
|
+
macaroon: body.macaroon ?? '',
|
|
75
|
+
payment_hash: body.payment_hash ?? '',
|
|
76
|
+
payment_url: `${this.valhallaUrl}/invoice-status/${body.payment_hash ?? ''}`,
|
|
77
|
+
amount_sats: body.amount_sats ?? 1000,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return {
|
|
82
|
+
status: 'payment_required',
|
|
83
|
+
message: 'Payment required — could not parse invoice details.',
|
|
84
|
+
invoice: '',
|
|
85
|
+
macaroon: '',
|
|
86
|
+
payment_hash: '',
|
|
87
|
+
payment_url: this.valhallaUrl,
|
|
88
|
+
amount_sats: 1000,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Type guard for L402 payment required responses. */
|
|
96
|
+
export function isPaymentRequired(result) {
|
|
97
|
+
return typeof result === 'object' && result !== null && result.status === 'payment_required';
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routing.js","sourceRoot":"","sources":["../src/routing.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { TransportMode } from 'rendezvous-kit';
|
|
3
|
+
import type { RoutingClient } from '../routing.js';
|
|
4
|
+
export declare function handleGetDirections(args: {
|
|
5
|
+
from: {
|
|
6
|
+
lat: number;
|
|
7
|
+
lon: number;
|
|
8
|
+
};
|
|
9
|
+
to: {
|
|
10
|
+
lat: number;
|
|
11
|
+
lon: number;
|
|
12
|
+
};
|
|
13
|
+
transport_mode: TransportMode;
|
|
14
|
+
}, routingClient: RoutingClient): Promise<{
|
|
15
|
+
content: {
|
|
16
|
+
type: "text";
|
|
17
|
+
text: string;
|
|
18
|
+
}[];
|
|
19
|
+
isError: true;
|
|
20
|
+
} | {
|
|
21
|
+
content: {
|
|
22
|
+
type: "text";
|
|
23
|
+
text: string;
|
|
24
|
+
}[];
|
|
25
|
+
isError?: undefined;
|
|
26
|
+
}>;
|
|
27
|
+
export declare function registerDirectionsTool(server: McpServer, routingClient: RoutingClient): void;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { isPaymentRequired } from '../routing.js';
|
|
3
|
+
export async function handleGetDirections(args, routingClient) {
|
|
4
|
+
console.error(`Computing route: ${args.transport_mode} from [${args.from.lat},${args.from.lon}] to [${args.to.lat},${args.to.lon}]`);
|
|
5
|
+
try {
|
|
6
|
+
const result = await routingClient.computeRoute({ lat: args.from.lat, lon: args.from.lon }, { lat: args.to.lat, lon: args.to.lon }, args.transport_mode);
|
|
7
|
+
if (isPaymentRequired(result)) {
|
|
8
|
+
return {
|
|
9
|
+
content: [{
|
|
10
|
+
type: 'text',
|
|
11
|
+
text: JSON.stringify({
|
|
12
|
+
success: false,
|
|
13
|
+
status: result.status,
|
|
14
|
+
message: result.message,
|
|
15
|
+
invoice: result.invoice,
|
|
16
|
+
macaroon: result.macaroon,
|
|
17
|
+
payment_hash: result.payment_hash,
|
|
18
|
+
payment_url: result.payment_url,
|
|
19
|
+
amount_sats: result.amount_sats,
|
|
20
|
+
}),
|
|
21
|
+
}],
|
|
22
|
+
isError: true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: JSON.stringify({
|
|
29
|
+
success: true,
|
|
30
|
+
distance_km: Math.round(result.distanceKm * 100) / 100,
|
|
31
|
+
duration_minutes: Math.round(result.durationMinutes * 10) / 10,
|
|
32
|
+
steps: (result.legs ?? []).map(leg => ({
|
|
33
|
+
instruction: leg.instruction,
|
|
34
|
+
distance_km: Math.round(leg.distanceKm * 100) / 100,
|
|
35
|
+
duration_minutes: Math.round(leg.durationMinutes * 10) / 10,
|
|
36
|
+
})),
|
|
37
|
+
}, null, 2),
|
|
38
|
+
}],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
43
|
+
return {
|
|
44
|
+
content: [{
|
|
45
|
+
type: 'text',
|
|
46
|
+
text: JSON.stringify({ success: false, error: message }),
|
|
47
|
+
}],
|
|
48
|
+
isError: true,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
export function registerDirectionsTool(server, routingClient) {
|
|
53
|
+
server.registerTool('get_directions', {
|
|
54
|
+
description: 'Get directions between two points with distance, duration, and turn-by-turn steps. ' +
|
|
55
|
+
'Returns a GeoJSON LineString of the route geometry.',
|
|
56
|
+
inputSchema: {
|
|
57
|
+
from: z.object({
|
|
58
|
+
lat: z.number().min(-90).max(90).describe('Latitude'),
|
|
59
|
+
lon: z.number().min(-180).max(180).describe('Longitude'),
|
|
60
|
+
}).describe('Starting point'),
|
|
61
|
+
to: z.object({
|
|
62
|
+
lat: z.number().min(-90).max(90).describe('Latitude'),
|
|
63
|
+
lon: z.number().min(-180).max(180).describe('Longitude'),
|
|
64
|
+
}).describe('Destination point'),
|
|
65
|
+
transport_mode: z.enum(['drive', 'cycle', 'walk']).describe('Travel mode'),
|
|
66
|
+
},
|
|
67
|
+
}, async (args) => handleGetDirections(args, routingClient));
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=directions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"directions.js","sourceRoot":"","sources":["../../src/tools/directions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { TransportMode } from 'rendezvous-kit';
|
|
3
|
+
import type { RoutingClient } from '../routing.js';
|
|
4
|
+
export declare function handleGetIsochrone(args: {
|
|
5
|
+
lat: number;
|
|
6
|
+
lon: number;
|
|
7
|
+
transport_mode: TransportMode;
|
|
8
|
+
time_minutes: number;
|
|
9
|
+
}, routingClient: RoutingClient): Promise<{
|
|
10
|
+
content: {
|
|
11
|
+
type: "text";
|
|
12
|
+
text: string;
|
|
13
|
+
}[];
|
|
14
|
+
isError: true;
|
|
15
|
+
} | {
|
|
16
|
+
content: {
|
|
17
|
+
type: "text";
|
|
18
|
+
text: string;
|
|
19
|
+
}[];
|
|
20
|
+
isError?: undefined;
|
|
21
|
+
}>;
|
|
22
|
+
export declare function registerIsochroneTool(server: McpServer, routingClient: RoutingClient): void;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { isPaymentRequired } from '../routing.js';
|
|
3
|
+
export async function handleGetIsochrone(args, routingClient) {
|
|
4
|
+
console.error(`Computing isochrone: ${args.time_minutes}min ${args.transport_mode} from [${args.lat},${args.lon}]`);
|
|
5
|
+
try {
|
|
6
|
+
const result = await routingClient.computeIsochrone({ lat: args.lat, lon: args.lon }, args.transport_mode, args.time_minutes);
|
|
7
|
+
if (isPaymentRequired(result)) {
|
|
8
|
+
return {
|
|
9
|
+
content: [{
|
|
10
|
+
type: 'text',
|
|
11
|
+
text: JSON.stringify({
|
|
12
|
+
success: false,
|
|
13
|
+
status: result.status,
|
|
14
|
+
message: result.message,
|
|
15
|
+
invoice: result.invoice,
|
|
16
|
+
macaroon: result.macaroon,
|
|
17
|
+
payment_hash: result.payment_hash,
|
|
18
|
+
payment_url: result.payment_url,
|
|
19
|
+
amount_sats: result.amount_sats,
|
|
20
|
+
}),
|
|
21
|
+
}],
|
|
22
|
+
isError: true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
content: [{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: JSON.stringify({
|
|
29
|
+
success: true,
|
|
30
|
+
time_minutes: result.timeMinutes,
|
|
31
|
+
transport_mode: result.mode,
|
|
32
|
+
polygon: result.polygon,
|
|
33
|
+
}, null, 2),
|
|
34
|
+
}],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
39
|
+
return {
|
|
40
|
+
content: [{
|
|
41
|
+
type: 'text',
|
|
42
|
+
text: JSON.stringify({ success: false, error: message }),
|
|
43
|
+
}],
|
|
44
|
+
isError: true,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function registerIsochroneTool(server, routingClient) {
|
|
49
|
+
server.registerTool('get_isochrone', {
|
|
50
|
+
description: 'Get a reachability polygon showing everywhere reachable from a point within a given travel time. ' +
|
|
51
|
+
'Returns a GeoJSON polygon. Useful for understanding how far someone can travel.',
|
|
52
|
+
inputSchema: {
|
|
53
|
+
lat: z.number().min(-90).max(90).describe('Latitude of starting point'),
|
|
54
|
+
lon: z.number().min(-180).max(180).describe('Longitude of starting point'),
|
|
55
|
+
transport_mode: z.enum(['drive', 'cycle', 'walk']).describe('Travel mode'),
|
|
56
|
+
time_minutes: z.number().min(1).max(120).describe('Maximum travel time in minutes'),
|
|
57
|
+
},
|
|
58
|
+
}, async (args) => handleGetIsochrone(args, routingClient));
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=isochrone.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"isochrone.js","sourceRoot":"","sources":["../../src/tools/isochrone.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { TransportMode, FairnessStrategy } from 'rendezvous-kit';
|
|
3
|
+
import type { RoutingClient } from '../routing.js';
|
|
4
|
+
export declare function handleScoreVenues(args: {
|
|
5
|
+
participants: Array<{
|
|
6
|
+
lat: number;
|
|
7
|
+
lon: number;
|
|
8
|
+
label?: string;
|
|
9
|
+
}>;
|
|
10
|
+
venues: Array<{
|
|
11
|
+
lat: number;
|
|
12
|
+
lon: number;
|
|
13
|
+
name: string;
|
|
14
|
+
type?: string;
|
|
15
|
+
}>;
|
|
16
|
+
transport_mode: TransportMode;
|
|
17
|
+
fairness?: FairnessStrategy;
|
|
18
|
+
}, routingClient: RoutingClient): Promise<{
|
|
19
|
+
content: {
|
|
20
|
+
type: "text";
|
|
21
|
+
text: string;
|
|
22
|
+
}[];
|
|
23
|
+
isError: true;
|
|
24
|
+
} | {
|
|
25
|
+
content: {
|
|
26
|
+
type: "text";
|
|
27
|
+
text: string;
|
|
28
|
+
}[];
|
|
29
|
+
isError?: undefined;
|
|
30
|
+
}>;
|
|
31
|
+
export declare function registerScoreVenuesTool(server: McpServer, routingClient: RoutingClient): void;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { isPaymentRequired } from '../routing.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Extracted handler (testable without MCP server)
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
export async function handleScoreVenues(args, routingClient) {
|
|
7
|
+
const fairness = args.fairness ?? 'min_max';
|
|
8
|
+
const participants = args.participants.map((p, i) => ({
|
|
9
|
+
lat: p.lat,
|
|
10
|
+
lon: p.lon,
|
|
11
|
+
label: p.label ?? `Participant ${i + 1}`,
|
|
12
|
+
}));
|
|
13
|
+
const venuePoints = args.venues.map(v => ({ lat: v.lat, lon: v.lon }));
|
|
14
|
+
console.error(`Scoring ${args.venues.length} venues for ${participants.length} participants (${args.transport_mode}, ${fairness})`);
|
|
15
|
+
try {
|
|
16
|
+
const matrix = await routingClient.computeRouteMatrix(participants, venuePoints, args.transport_mode);
|
|
17
|
+
if (isPaymentRequired(matrix)) {
|
|
18
|
+
return {
|
|
19
|
+
content: [{
|
|
20
|
+
type: 'text',
|
|
21
|
+
text: JSON.stringify({
|
|
22
|
+
success: false,
|
|
23
|
+
status: matrix.status,
|
|
24
|
+
message: matrix.message,
|
|
25
|
+
invoice: matrix.invoice,
|
|
26
|
+
macaroon: matrix.macaroon,
|
|
27
|
+
payment_hash: matrix.payment_hash,
|
|
28
|
+
payment_url: matrix.payment_url,
|
|
29
|
+
amount_sats: matrix.amount_sats,
|
|
30
|
+
}),
|
|
31
|
+
}],
|
|
32
|
+
isError: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Score each venue
|
|
36
|
+
const ranked = [];
|
|
37
|
+
for (let vi = 0; vi < args.venues.length; vi++) {
|
|
38
|
+
const venue = args.venues[vi];
|
|
39
|
+
const travelTimes = {};
|
|
40
|
+
const times = [];
|
|
41
|
+
let reachable = true;
|
|
42
|
+
for (let pi = 0; pi < participants.length; pi++) {
|
|
43
|
+
const entry = matrix.entries.find(e => e.originIndex === pi && e.destinationIndex === vi);
|
|
44
|
+
const duration = entry?.durationMinutes ?? -1;
|
|
45
|
+
if (duration < 0) {
|
|
46
|
+
reachable = false;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
const label = participants[pi].label;
|
|
50
|
+
travelTimes[label] = Math.round(duration * 10) / 10;
|
|
51
|
+
times.push(duration);
|
|
52
|
+
}
|
|
53
|
+
if (!reachable)
|
|
54
|
+
continue;
|
|
55
|
+
const fairnessScore = computeFairnessScore(times, fairness);
|
|
56
|
+
ranked.push({
|
|
57
|
+
name: venue.name,
|
|
58
|
+
lat: venue.lat,
|
|
59
|
+
lon: venue.lon,
|
|
60
|
+
type: venue.type,
|
|
61
|
+
travel_times: travelTimes,
|
|
62
|
+
fairness_score: Math.round(fairnessScore * 10) / 10,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
ranked.sort((a, b) => a.fairness_score - b.fairness_score);
|
|
66
|
+
return {
|
|
67
|
+
content: [{
|
|
68
|
+
type: 'text',
|
|
69
|
+
text: JSON.stringify({ success: true, ranked_venues: ranked }, null, 2),
|
|
70
|
+
}],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
75
|
+
return {
|
|
76
|
+
content: [{
|
|
77
|
+
type: 'text',
|
|
78
|
+
text: JSON.stringify({ success: false, error: message }),
|
|
79
|
+
}],
|
|
80
|
+
isError: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function computeFairnessScore(times, strategy) {
|
|
85
|
+
switch (strategy) {
|
|
86
|
+
case 'min_max':
|
|
87
|
+
return Math.max(...times);
|
|
88
|
+
case 'min_total':
|
|
89
|
+
return times.reduce((sum, t) => sum + t, 0);
|
|
90
|
+
case 'min_variance': {
|
|
91
|
+
const mean = times.reduce((sum, t) => sum + t, 0) / times.length;
|
|
92
|
+
return Math.sqrt(times.reduce((sum, t) => sum + (t - mean) ** 2, 0) / times.length);
|
|
93
|
+
}
|
|
94
|
+
default:
|
|
95
|
+
return Math.max(...times);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Tool registration
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
export function registerScoreVenuesTool(server, routingClient) {
|
|
102
|
+
server.registerTool('score_venues', {
|
|
103
|
+
description: 'Score candidate venues by travel time fairness for multiple participants. ' +
|
|
104
|
+
'Computes travel times from each participant to each venue and ranks by fairness strategy. ' +
|
|
105
|
+
'The AI should suggest venues (from its own knowledge or via search_venues) and pass them here for scoring.',
|
|
106
|
+
inputSchema: {
|
|
107
|
+
participants: z.array(z.object({
|
|
108
|
+
lat: z.number().min(-90).max(90).describe('Latitude'),
|
|
109
|
+
lon: z.number().min(-180).max(180).describe('Longitude'),
|
|
110
|
+
label: z.string().optional().describe('Name or label (e.g. "Alice")'),
|
|
111
|
+
})).min(2).max(10).describe('Participant locations (2–10 people)'),
|
|
112
|
+
venues: z.array(z.object({
|
|
113
|
+
lat: z.number().min(-90).max(90).describe('Latitude'),
|
|
114
|
+
lon: z.number().min(-180).max(180).describe('Longitude'),
|
|
115
|
+
name: z.string().describe('Venue name'),
|
|
116
|
+
type: z.string().optional().describe('Venue type (pub, cafe, restaurant, park, etc.)'),
|
|
117
|
+
})).min(1).max(50).describe('Candidate venues to score (1–50)'),
|
|
118
|
+
transport_mode: z.enum(['drive', 'cycle', 'walk']).describe('How participants will travel'),
|
|
119
|
+
fairness: z.enum(['min_max', 'min_total', 'min_variance']).optional()
|
|
120
|
+
.describe('Scoring strategy: min_max (default, minimise longest journey), min_total (minimise total travel), min_variance (equalise travel times)'),
|
|
121
|
+
},
|
|
122
|
+
}, async (args) => handleScoreVenues(args, routingClient));
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=score-venues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score-venues.js","sourceRoot":"","sources":["../../src/tools/score-venues.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
export declare function handleSearchVenues(args: {
|
|
3
|
+
lat: number;
|
|
4
|
+
lon: number;
|
|
5
|
+
radius_km?: number;
|
|
6
|
+
venue_types: string[];
|
|
7
|
+
}, overpassUrl?: string): Promise<{
|
|
8
|
+
content: {
|
|
9
|
+
type: "text";
|
|
10
|
+
text: string;
|
|
11
|
+
}[];
|
|
12
|
+
isError?: undefined;
|
|
13
|
+
} | {
|
|
14
|
+
content: {
|
|
15
|
+
type: "text";
|
|
16
|
+
text: string;
|
|
17
|
+
}[];
|
|
18
|
+
isError: true;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function registerSearchVenuesTool(server: McpServer, overpassUrl?: string): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { searchVenues, circleToPolygon } from 'rendezvous-kit';
|
|
3
|
+
const VENUE_TYPES = [
|
|
4
|
+
'pub', 'cafe', 'restaurant', 'park', 'library', 'playground',
|
|
5
|
+
'community_centre', 'bar', 'fast_food', 'garden', 'theatre',
|
|
6
|
+
'arts_centre', 'fitness_centre', 'sports_centre', 'escape_game',
|
|
7
|
+
'swimming_pool', 'service_station',
|
|
8
|
+
];
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Extracted handler
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
export async function handleSearchVenues(args, overpassUrl) {
|
|
13
|
+
const radiusMetres = (args.radius_km ?? 5) * 1000;
|
|
14
|
+
console.error(`Searching venues within ${args.radius_km ?? 5}km of [${args.lat},${args.lon}] for types: ${args.venue_types.join(', ')}`);
|
|
15
|
+
try {
|
|
16
|
+
const polygon = circleToPolygon([args.lon, args.lat], radiusMetres);
|
|
17
|
+
const venues = await searchVenues(polygon, args.venue_types, overpassUrl);
|
|
18
|
+
return {
|
|
19
|
+
content: [{
|
|
20
|
+
type: 'text',
|
|
21
|
+
text: JSON.stringify({
|
|
22
|
+
success: true,
|
|
23
|
+
venue_count: venues.length,
|
|
24
|
+
venues: venues.map(v => ({
|
|
25
|
+
name: v.name,
|
|
26
|
+
lat: v.lat,
|
|
27
|
+
lon: v.lon,
|
|
28
|
+
type: v.venueType,
|
|
29
|
+
osm_id: v.osmId,
|
|
30
|
+
})),
|
|
31
|
+
}, null, 2),
|
|
32
|
+
}],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: 'text',
|
|
40
|
+
text: JSON.stringify({ success: false, error: message }),
|
|
41
|
+
}],
|
|
42
|
+
isError: true,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Tool registration
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
export function registerSearchVenuesTool(server, overpassUrl) {
|
|
50
|
+
server.registerTool('search_venues', {
|
|
51
|
+
description: 'Search for venues (pubs, cafes, restaurants, parks, etc.) near a location using OpenStreetMap data. ' +
|
|
52
|
+
'Returns name, coordinates, type, and OSM ID. Use this when you need comprehensive local venue data ' +
|
|
53
|
+
'that may not be in your training knowledge.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
lat: z.number().min(-90).max(90).describe('Centre latitude'),
|
|
56
|
+
lon: z.number().min(-180).max(180).describe('Centre longitude'),
|
|
57
|
+
radius_km: z.number().min(0.5).max(25).optional().describe('Search radius in km (default 5)'),
|
|
58
|
+
venue_types: z.array(z.enum(VENUE_TYPES)).min(1)
|
|
59
|
+
.describe('Venue types to search: pub, cafe, restaurant, park, library, playground, community_centre, bar, fast_food, garden, theatre, arts_centre, fitness_centre, sports_centre, escape_game, swimming_pool, service_station'),
|
|
60
|
+
},
|
|
61
|
+
}, async (args) => handleSearchVenues(args, overpassUrl));
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=search-venues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-venues.js","sourceRoot":"","sources":["../../src/tools/search-venues.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { RoutingClient } from '../routing.js';
|
|
3
|
+
export declare function handleStoreRoutingCredentials(args: {
|
|
4
|
+
macaroon: string;
|
|
5
|
+
preimage: string;
|
|
6
|
+
}, routingClient: RoutingClient): Promise<{
|
|
7
|
+
content: {
|
|
8
|
+
type: "text";
|
|
9
|
+
text: string;
|
|
10
|
+
}[];
|
|
11
|
+
}>;
|
|
12
|
+
export declare function registerStoreCredentialsTool(server: McpServer, routingClient: RoutingClient): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Extracted handler (testable without MCP server)
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
export async function handleStoreRoutingCredentials(args, routingClient) {
|
|
6
|
+
console.error('Storing L402 routing credentials');
|
|
7
|
+
routingClient.storeL402Credentials(args.macaroon, args.preimage);
|
|
8
|
+
return {
|
|
9
|
+
content: [{
|
|
10
|
+
type: 'text',
|
|
11
|
+
text: JSON.stringify({
|
|
12
|
+
success: true,
|
|
13
|
+
message: 'L402 credentials stored. Subsequent routing calls will authenticate automatically.',
|
|
14
|
+
}),
|
|
15
|
+
}],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Tool registration
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
export function registerStoreCredentialsTool(server, routingClient) {
|
|
22
|
+
server.registerTool('store_routing_credentials', {
|
|
23
|
+
description: 'Store L402 payment credentials (macaroon + preimage) after paying a routing invoice. ' +
|
|
24
|
+
'Call this after the user has paid the Lightning invoice returned by a payment_required response. ' +
|
|
25
|
+
'Once stored, all subsequent routing calls (find_rendezvous, score_venues, get_isochrone, get_directions) ' +
|
|
26
|
+
'will authenticate automatically.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
macaroon: z.string().min(1).regex(/^[A-Za-z0-9+/=]+$/, 'Must be valid base64')
|
|
29
|
+
.describe('The macaroon from the payment_required response'),
|
|
30
|
+
preimage: z.string().min(1).regex(/^[0-9a-fA-F]+$/, 'Must be valid hex')
|
|
31
|
+
.describe('The payment preimage obtained after paying the invoice'),
|
|
32
|
+
},
|
|
33
|
+
}, async (args) => handleStoreRoutingCredentials(args, routingClient));
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=store-credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store-credentials.js","sourceRoot":"","sources":["../../src/tools/store-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,8EAA8E;AAC9E,kDAAkD;AAClD,8EAA8E;AAE9E,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,IAGC,EACD,aAA4B;IAE5B,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAA;IAEjD,aAAa,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;IAEhE,OAAO;QACL,OAAO,EAAE,CAAC;gBACR,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,OAAO,EAAE,IAAI;oBACb,OAAO,EAAE,oFAAoF;iBAC9F,CAAC;aACH,CAAC;KACH,CAAA;AACH,CAAC;AAED,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E,MAAM,UAAU,4BAA4B,CAAC,MAAiB,EAAE,aAA4B;IAC1F,MAAM,CAAC,YAAY,CACjB,2BAA2B,EAC3B;QACE,WAAW,EACT,uFAAuF;YACvF,mGAAmG;YACnG,2GAA2G;YAC3G,kCAAkC;QACpC,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,mBAAmB,EAAE,sBAAsB,CAAC;iBAC3E,QAAQ,CAAC,iDAAiD,CAAC;YAC9D,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,gBAAgB,EAAE,mBAAmB,CAAC;iBACrE,QAAQ,CAAC,wDAAwD,CAAC;SACtE;KACF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,6BAA6B,CAAC,IAAI,EAAE,aAAa,CAAC,CACnE,CAAA;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rendezvous-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for AI-driven fair meeting point discovery",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"rendezvous-mcp": "./build/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./build/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./build/index.js",
|
|
12
|
+
"./routing": "./build/routing.js",
|
|
13
|
+
"./l402": "./build/l402.js",
|
|
14
|
+
"./tools/*": "./build/tools/*.js"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"test": "vitest run",
|
|
19
|
+
"test:watch": "vitest",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"start": "node build/index.js",
|
|
22
|
+
"start:http": "TRANSPORT=http node build/index.js"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
26
|
+
"cors": "^2.8.5",
|
|
27
|
+
"express": "^4.21.0",
|
|
28
|
+
"rendezvous-kit": "^1.17.2",
|
|
29
|
+
"zod": "^3.24.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
33
|
+
"@semantic-release/git": "^10.0.1",
|
|
34
|
+
"@semantic-release/github": "^12.0.6",
|
|
35
|
+
"@semantic-release/npm": "^13.1.4",
|
|
36
|
+
"@types/cors": "^2.8.17",
|
|
37
|
+
"@types/express": "^5.0.0",
|
|
38
|
+
"@types/node": "^22.0.0",
|
|
39
|
+
"semantic-release": "^25.0.3",
|
|
40
|
+
"typescript": "^5.7.0",
|
|
41
|
+
"vitest": "^4.0.18"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"mcp",
|
|
45
|
+
"meeting-point",
|
|
46
|
+
"rendezvous",
|
|
47
|
+
"routing",
|
|
48
|
+
"valhalla",
|
|
49
|
+
"l402",
|
|
50
|
+
"model-context-protocol",
|
|
51
|
+
"ai",
|
|
52
|
+
"fairness",
|
|
53
|
+
"isochrone",
|
|
54
|
+
"travel-time",
|
|
55
|
+
"openstreetmap",
|
|
56
|
+
"geospatial",
|
|
57
|
+
"venue-search"
|
|
58
|
+
],
|
|
59
|
+
"sideEffects": false,
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=18"
|
|
62
|
+
},
|
|
63
|
+
"author": "forgesworn",
|
|
64
|
+
"repository": {
|
|
65
|
+
"type": "git",
|
|
66
|
+
"url": "https://github.com/forgesworn/rendezvous-mcp.git"
|
|
67
|
+
},
|
|
68
|
+
"homepage": "https://github.com/forgesworn/rendezvous-mcp",
|
|
69
|
+
"funding": {
|
|
70
|
+
"type": "lightning",
|
|
71
|
+
"url": "https://strike.me/thedonkey"
|
|
72
|
+
},
|
|
73
|
+
"files": [
|
|
74
|
+
"build",
|
|
75
|
+
"LICENSE"
|
|
76
|
+
],
|
|
77
|
+
"license": "MIT"
|
|
78
|
+
}
|