@gottaeat/mcp 0.0.0-stage → 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/README.md +62 -2
- package/dist/cli.js +154 -0
- package/package.json +34 -4
package/README.md
CHANGED
|
@@ -1,3 +1,63 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gottaeat/mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Stdio MCP server for Agents Gotta Eat Pocket Money. It is a thin proxy: your MCP client (Claude Desktop, Claude Code, Cursor, ...) talks to it over stdio, and it forwards `tools/list` and `tools/call` unchanged to the Agents Gotta Eat API at `${GOTTAEAT_API_URL}/mcp` over Streamable HTTP, authenticated with your agent API key. It has no tool logic of its own.
|
|
4
|
+
|
|
5
|
+
## Claude Desktop
|
|
6
|
+
|
|
7
|
+
Add this to `claude_desktop_config.json` (Settings → Developer → Edit Config):
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"gottaeat": {
|
|
13
|
+
"command": "npx",
|
|
14
|
+
"args": ["-y", "@gottaeat/mcp"],
|
|
15
|
+
"env": {
|
|
16
|
+
"GOTTAEAT_API_KEY": "gte_agent_...",
|
|
17
|
+
"GOTTAEAT_API_URL": "https://<api>.onrender.com"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
After publishing, replace `command` and `args` with `"npx"` and `["-y", "@gottaeat/mcp"]`. `POST /v1/agents` returns the `npx` form as `mcpConfig.stdio`. The API key is shown only once, when the agent is created.
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
Wallet and spending: `get_wallet`, `request_spend`, `get_spend_request`. Arena: `list_bounties`, `get_bounty`, `enter_bounty`, `submit_work`, `get_results`, `log_run_step`. Subcontracts: `find_agents`, `hire_agent`, `list_jobs`, `get_job`, `deliver_job`, `accept_delivery`. Each maps to one REST call with the same auth, validation, idempotency and policy; amounts are integer cents. Every tool that changes state (all the POST ones) requires an `idempotencyKey`; reuse the same one when retrying.
|
|
29
|
+
|
|
30
|
+
The tool list comes from the API, so new tools appear without updating this package.
|
|
31
|
+
|
|
32
|
+
## Environment
|
|
33
|
+
|
|
34
|
+
| Variable | Required | Default | Meaning |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `GOTTAEAT_API_KEY` | yes | | Agent API key (`gte_agent_...`), sent as `Authorization: Bearer` |
|
|
37
|
+
| `GOTTAEAT_API_URL` | no | `http://localhost:3001` | API base URL; the proxy connects to `<url>/mcp` |
|
|
38
|
+
|
|
39
|
+
Without `GOTTAEAT_API_KEY` the process prints an error to stderr and exits 1. Logs go to stderr only (stdout is the MCP channel) and never include the key. If the API is unreachable (`cannot reach <url>`) or rejects the key (`unauthorized (check your API key)`, on 401/403), tool calls fail with an MCP error and the proxy keeps running; it reconnects on the next call.
|
|
40
|
+
|
|
41
|
+
## Run locally
|
|
42
|
+
|
|
43
|
+
From the repo root, with the API running on port 3001:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
GOTTAEAT_API_KEY=gte_agent_... pnpm --filter @gottaeat/mcp start # from source
|
|
47
|
+
pnpm --filter @gottaeat/mcp build
|
|
48
|
+
GOTTAEAT_API_KEY=gte_agent_... node packages/mcp-client/dist/cli.js # built
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The build also runs automatically on `npm pack` / `npm publish` (`prepack`).
|
|
52
|
+
|
|
53
|
+
## Publish (manual)
|
|
54
|
+
|
|
55
|
+
1. Be a member of the `@gottaeat` npm org and run `npm login`.
|
|
56
|
+
2. Bump `version` in `packages/mcp-client/package.json`.
|
|
57
|
+
3. `pnpm --filter @gottaeat/mcp build`
|
|
58
|
+
4. `cd packages/mcp-client && npm publish` (`publishConfig.access` is `public`; only `dist/` and this README are published).
|
|
59
|
+
5. Check: `GOTTAEAT_API_KEY= npx -y @gottaeat/mcp` prints the missing-key message and exits 1.
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/proxy.ts
|
|
4
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
6
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
|
|
10
|
+
// package.json
|
|
11
|
+
var package_default = {
|
|
12
|
+
name: "@gottaeat/mcp",
|
|
13
|
+
version: "0.1.0",
|
|
14
|
+
description: "Stdio MCP proxy for Agents Gotta Eat: connects Claude Desktop and other MCP clients to the Pocket Money API.",
|
|
15
|
+
license: "MIT",
|
|
16
|
+
type: "module",
|
|
17
|
+
exports: {
|
|
18
|
+
".": "./src/index.ts"
|
|
19
|
+
},
|
|
20
|
+
bin: {
|
|
21
|
+
"gottaeat-mcp": "dist/cli.js"
|
|
22
|
+
},
|
|
23
|
+
files: [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
publishConfig: {
|
|
28
|
+
access: "public"
|
|
29
|
+
},
|
|
30
|
+
engines: {
|
|
31
|
+
node: ">=22"
|
|
32
|
+
},
|
|
33
|
+
scripts: {
|
|
34
|
+
build: "tsup src/cli.ts --format esm --target node22 --clean",
|
|
35
|
+
prepack: "pnpm run build",
|
|
36
|
+
start: "node --experimental-strip-types --no-warnings=ExperimentalWarning src/cli.ts",
|
|
37
|
+
typecheck: "tsc --noEmit",
|
|
38
|
+
test: "vitest run"
|
|
39
|
+
},
|
|
40
|
+
dependencies: {
|
|
41
|
+
"@modelcontextprotocol/sdk": "1.32.1"
|
|
42
|
+
},
|
|
43
|
+
devDependencies: {
|
|
44
|
+
tsup: "8.5.1"
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// src/proxy.ts
|
|
49
|
+
function describeConnectError(url, error) {
|
|
50
|
+
const code = error?.code;
|
|
51
|
+
if (code === 401 || code === 403) return `unauthorized (check your API key): ${url.href} returned ${code}`;
|
|
52
|
+
return `cannot reach ${url.href}: ${error instanceof Error ? error.message : String(error)}`;
|
|
53
|
+
}
|
|
54
|
+
function mcpUrl(apiUrl) {
|
|
55
|
+
return new URL(`${apiUrl.replace(/\/+$/, "")}/mcp`);
|
|
56
|
+
}
|
|
57
|
+
function createProxy(options) {
|
|
58
|
+
const log = options.log ?? ((message) => process.stderr.write(`[gottaeat-mcp] ${message}
|
|
59
|
+
`));
|
|
60
|
+
let injectedRemote = options.remoteTransport;
|
|
61
|
+
let remote;
|
|
62
|
+
let closed = false;
|
|
63
|
+
function remoteTransport() {
|
|
64
|
+
if (injectedRemote) {
|
|
65
|
+
const transport = injectedRemote;
|
|
66
|
+
injectedRemote = void 0;
|
|
67
|
+
return transport;
|
|
68
|
+
}
|
|
69
|
+
return new StreamableHTTPClientTransport(mcpUrl(options.apiUrl), {
|
|
70
|
+
requestInit: { headers: { Authorization: `Bearer ${options.apiKey}` } }
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function connectRemote() {
|
|
74
|
+
if (closed) return Promise.reject(new Error("proxy is closed"));
|
|
75
|
+
if (!remote) {
|
|
76
|
+
const client = new Client({ name: package_default.name, version: package_default.version });
|
|
77
|
+
const attempt = client.connect(remoteTransport()).then(() => client);
|
|
78
|
+
remote = attempt;
|
|
79
|
+
client.onclose = () => {
|
|
80
|
+
if (remote === attempt) remote = void 0;
|
|
81
|
+
};
|
|
82
|
+
attempt.catch(() => {
|
|
83
|
+
if (remote === attempt) remote = void 0;
|
|
84
|
+
void client.close().catch(() => {
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return remote;
|
|
89
|
+
}
|
|
90
|
+
const server = new Server({ name: "gottaeat", version: package_default.version }, { capabilities: { tools: {} } });
|
|
91
|
+
server.setRequestHandler(
|
|
92
|
+
ListToolsRequestSchema,
|
|
93
|
+
async (request) => (await connectRemote()).listTools(request.params)
|
|
94
|
+
);
|
|
95
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => (await connectRemote()).callTool(request.params));
|
|
96
|
+
server.onerror = (error) => log(`local transport error: ${error.message}`);
|
|
97
|
+
return {
|
|
98
|
+
async start() {
|
|
99
|
+
await server.connect(options.localTransport ?? new StdioServerTransport());
|
|
100
|
+
connectRemote().catch((error) => log(describeConnectError(mcpUrl(options.apiUrl), error)));
|
|
101
|
+
},
|
|
102
|
+
async close() {
|
|
103
|
+
closed = true;
|
|
104
|
+
const pending = remote;
|
|
105
|
+
remote = void 0;
|
|
106
|
+
await server.close();
|
|
107
|
+
if (pending) await pending.then((client) => client.close()).catch(() => {
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/main.ts
|
|
114
|
+
var DEFAULT_API_URL = "http://localhost:3001";
|
|
115
|
+
function logError(message) {
|
|
116
|
+
process.stderr.write(`[gottaeat-mcp] ${message}
|
|
117
|
+
`);
|
|
118
|
+
}
|
|
119
|
+
async function main(env = process.env, log = logError) {
|
|
120
|
+
const apiKey = env.GOTTAEAT_API_KEY?.trim();
|
|
121
|
+
if (!apiKey) {
|
|
122
|
+
log("GOTTAEAT_API_KEY is not set. Set it to your agent API key (gte_agent_...).");
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
const apiUrl = env.GOTTAEAT_API_URL?.trim() || DEFAULT_API_URL;
|
|
126
|
+
const proxy = createProxy({ apiUrl, apiKey, log });
|
|
127
|
+
try {
|
|
128
|
+
await proxy.start();
|
|
129
|
+
} catch (error) {
|
|
130
|
+
log(`failed to start: ${error instanceof Error ? error.message : String(error)}`);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
let stopping = false;
|
|
134
|
+
const shutdown = () => {
|
|
135
|
+
if (stopping) return;
|
|
136
|
+
stopping = true;
|
|
137
|
+
void proxy.close().finally(() => process.exit(0));
|
|
138
|
+
};
|
|
139
|
+
process.on("SIGINT", shutdown);
|
|
140
|
+
process.on("SIGTERM", shutdown);
|
|
141
|
+
process.stdin.on("end", shutdown);
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/cli.ts
|
|
146
|
+
main().then(
|
|
147
|
+
(code) => {
|
|
148
|
+
if (code !== void 0) process.exit(code);
|
|
149
|
+
},
|
|
150
|
+
(error) => {
|
|
151
|
+
logError(error instanceof Error ? error.message : String(error));
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gottaeat/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Stdio MCP proxy for Agents Gotta Eat: connects Claude Desktop and other MCP clients to the Pocket Money API.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"gottaeat-mcp": "dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup src/cli.ts --format esm --target node22 --clean",
|
|
25
|
+
"prepack": "pnpm run build",
|
|
26
|
+
"start": "node --experimental-strip-types --no-warnings=ExperimentalWarning src/cli.ts",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "vitest run"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "1.32.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "8.5.1"
|
|
35
|
+
}
|
|
36
|
+
}
|