@tuppence/mcp 0.0.0-stage → 0.1.1
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 +31 -2
- package/dist/cli.js +68 -0
- package/package.json +35 -3
package/README.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tuppence/mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Tuppence's MCP tools for clients that only speak **stdio**. Tuppence's MCP server is remote
|
|
4
|
+
(`https://api.tuppence.ai/mcp`); this is a small local server that forwards to it with your key,
|
|
5
|
+
so the tools are exactly the remote ones, scoped to what that key may do.
|
|
6
|
+
|
|
7
|
+
Clients that support remote (HTTP) MCP servers don't need this: point them at
|
|
8
|
+
`https://api.tuppence.ai/mcp` with an `Authorization: Bearer <key>` header.
|
|
9
|
+
|
|
10
|
+
## Claude Code
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
claude mcp add tuppence --env TUPPENCE_API_KEY=sk_test_… -- npx -y @tuppence/mcp
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Claude Desktop / Cursor (`mcpServers` config)
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"tuppence": {
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["-y", "@tuppence/mcp"],
|
|
24
|
+
"env": { "TUPPENCE_API_KEY": "sk_test_…" }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Use a **restricted key** (`rk_…`) with only the scopes the agent needs: it will only see the
|
|
31
|
+
tools those scopes allow. Prefer `TUPPENCE_API_KEY` to `--api-key`: command lines are visible to
|
|
32
|
+
other processes. `TUPPENCE_API_URL` (or `--api-url`) points it somewhere other than production.
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `npx @tuppence/mcp` — Tuppence's MCP server for clients that only speak stdio (plan 11-07).
|
|
4
|
+
*
|
|
5
|
+
* Tuppence's MCP server is remote (`POST https://api.tuppence.ai/mcp`, streamable HTTP). This
|
|
6
|
+
* is a local stdio server that forwards `tools/list` and `tools/call` to it, authenticated with
|
|
7
|
+
* your API key — so the tools, their scopes and their behaviour are exactly the remote ones.
|
|
8
|
+
*
|
|
9
|
+
* TUPPENCE_API_KEY=sk_test_… npx @tuppence/mcp
|
|
10
|
+
*
|
|
11
|
+
* Prefer the environment variable to `--api-key`: command lines are visible to every process
|
|
12
|
+
* on the machine.
|
|
13
|
+
*
|
|
14
|
+
* Tuppence Connect: `TUPPENCE_ACCOUNT=acct_…` (or `--account`) makes every tool act as that one
|
|
15
|
+
* of the key's connected accounts — the `Tuppence-Account` header on the connection.
|
|
16
|
+
*/
|
|
17
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
18
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
19
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
20
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
22
|
+
const VERSION = "0.1.1";
|
|
23
|
+
function option(name) {
|
|
24
|
+
const at = process.argv.indexOf(`--${name}`);
|
|
25
|
+
if (at >= 0)
|
|
26
|
+
return process.argv[at + 1];
|
|
27
|
+
const inline = process.argv.find((a) => a.startsWith(`--${name}=`));
|
|
28
|
+
return inline?.slice(name.length + 3);
|
|
29
|
+
}
|
|
30
|
+
/** Everything goes to stderr: stdout is the protocol. */
|
|
31
|
+
const fail = (message) => {
|
|
32
|
+
process.stderr.write(`tuppence-mcp: ${message}\n`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
};
|
|
35
|
+
const apiKey = process.env.TUPPENCE_API_KEY ?? option("api-key");
|
|
36
|
+
const apiUrl = (process.env.TUPPENCE_API_URL ??
|
|
37
|
+
option("api-url") ??
|
|
38
|
+
"https://api.tuppence.ai").replace(/\/+$/, "");
|
|
39
|
+
if (!apiKey)
|
|
40
|
+
fail("set TUPPENCE_API_KEY (or pass --api-key) to a Tuppence secret or restricted key.");
|
|
41
|
+
if (!/^(sk|rk)_(test|live)_/.test(apiKey)) {
|
|
42
|
+
fail("that is not a Tuppence secret or restricted key (sk_… / rk_…).");
|
|
43
|
+
}
|
|
44
|
+
const account = process.env.TUPPENCE_ACCOUNT ?? option("account");
|
|
45
|
+
if (account !== undefined && !/^acct_\w+$/.test(account)) {
|
|
46
|
+
fail('TUPPENCE_ACCOUNT (or --account) is a connected account id: "acct_…".');
|
|
47
|
+
}
|
|
48
|
+
const remote = new Client({ name: "tuppence-mcp-stdio", version: VERSION });
|
|
49
|
+
await remote
|
|
50
|
+
.connect(new StreamableHTTPClientTransport(new URL(`${apiUrl}/mcp`), {
|
|
51
|
+
requestInit: {
|
|
52
|
+
headers: {
|
|
53
|
+
Authorization: `Bearer ${apiKey}`,
|
|
54
|
+
...(account ? { "Tuppence-Account": account } : {}),
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
}))
|
|
58
|
+
.catch((error) => fail(`could not reach ${apiUrl}/mcp: ${error.message}`));
|
|
59
|
+
const local = new Server({ name: "tuppence", version: VERSION }, {
|
|
60
|
+
capabilities: { tools: {} },
|
|
61
|
+
instructions: "Tuppence: take payments, create payment links, refund, read balances and meter usage. Tools act as the API key this server was started with.",
|
|
62
|
+
});
|
|
63
|
+
local.setRequestHandler(ListToolsRequestSchema, (request) => remote.listTools(request.params));
|
|
64
|
+
local.setRequestHandler(CallToolRequestSchema, (request) => remote.callTool(request.params));
|
|
65
|
+
await local.connect(new StdioServerTransport());
|
|
66
|
+
process.stdin.on("close", () => {
|
|
67
|
+
void remote.close().finally(() => process.exit(0));
|
|
68
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tuppence/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Tuppence's MCP server for clients that only speak stdio: a local proxy to the remote server.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"homepage": "https://docs.tuppence.ai",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/ZenningAI/tuppence.git",
|
|
10
|
+
"directory": "packages/mcp"
|
|
11
|
+
},
|
|
12
|
+
"bugs": "https://github.com/ZenningAI/tuppence/issues",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=18"
|
|
16
|
+
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"tuppence-mcp": "./dist/cli.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "1.32.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^22.20.4",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
36
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
37
|
+
}
|
|
6
38
|
}
|