gliana-ai-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/README.md +62 -0
- package/dist/index.js +120 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# GlianaAI MCP server
|
|
2
|
+
|
|
3
|
+
Pay-per-call generative AI for any MCP client (Claude Desktop, Cursor, …).
|
|
4
|
+
**59 models** — image, video, music, speech — with **no signup and no API key**.
|
|
5
|
+
Each `generate` is paid per call from **your own wallet** over MPP / x402.
|
|
6
|
+
|
|
7
|
+
- Browse + price models for free (`list_models`, `get_price`, `get_schema`).
|
|
8
|
+
- `generate` runs a model and settles the gateway's 402 from your wallet (USDC on
|
|
9
|
+
Base). Your private key is read from the client config and **never leaves your
|
|
10
|
+
machine** — non-custodial, same model as [ai.glianalabs.com](https://ai.glianalabs.com).
|
|
11
|
+
|
|
12
|
+
## Tools
|
|
13
|
+
|
|
14
|
+
| Tool | Paid? | Description |
|
|
15
|
+
|------|-------|-------------|
|
|
16
|
+
| `list_models` | free | Every model: id, category, provider, per-call price. |
|
|
17
|
+
| `get_price` | free | Exact cost of one call (input affects it — video duration, TTS length). |
|
|
18
|
+
| `get_schema` | free | A model's input fields (required, defaults). |
|
|
19
|
+
| `generate` | **paid** | Run a model → media URL. Pays from your wallet. |
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
Add to your MCP client config.
|
|
24
|
+
|
|
25
|
+
**Claude Desktop** (`claude_desktop_config.json`) / **Cursor** (`~/.cursor/mcp.json`):
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"mcpServers": {
|
|
30
|
+
"gliana-ai": {
|
|
31
|
+
"command": "npx",
|
|
32
|
+
"args": ["-y", "gliana-ai-mcp"],
|
|
33
|
+
"env": {
|
|
34
|
+
"GLIANA_WALLET_KEY": "0xYOUR_PRIVATE_KEY"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- `GLIANA_WALLET_KEY` — a `0x` private key for a wallet holding **USDC on Base**.
|
|
42
|
+
Required only for `generate`; omit it and the discovery tools still work.
|
|
43
|
+
- `GLIANA_API_URL` — optional, defaults to `https://api.glianalabs.com`.
|
|
44
|
+
|
|
45
|
+
Restart the client. Ask it to *"list GlianaAI models"* or *"generate an image of a
|
|
46
|
+
red fox with nano-banana-2"*.
|
|
47
|
+
|
|
48
|
+
## Funding
|
|
49
|
+
|
|
50
|
+
Fund the wallet with a few dollars of USDC on Base. You pay only the per-call
|
|
51
|
+
price (see `get_price`); there's no subscription and no balance held by us.
|
|
52
|
+
|
|
53
|
+
> Use a dedicated low-balance wallet for agents. Never paste your main wallet's
|
|
54
|
+
> key into any config.
|
|
55
|
+
|
|
56
|
+
## Links
|
|
57
|
+
|
|
58
|
+
- Website: https://ai.glianalabs.com
|
|
59
|
+
- API docs: https://ai.glianalabs.com/docs
|
|
60
|
+
- Discoverable on [mppscan](https://mppscan.com) and [x402scan](https://www.x402scan.com)
|
|
61
|
+
|
|
62
|
+
MIT © Gliana Labs
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* GlianaAI MCP server — exposes pay-per-call generative AI (59 models: image,
|
|
4
|
+
* video, music, speech) to any MCP client (Claude Desktop, Cursor, …).
|
|
5
|
+
*
|
|
6
|
+
* Discovery tools (list_models / get_price / get_schema) are free. `generate`
|
|
7
|
+
* runs a model and settles the gateway's 402 challenge from YOUR wallet via mppx
|
|
8
|
+
* (USDC on Base). The key is read from GLIANA_WALLET_KEY in the client config and
|
|
9
|
+
* never leaves this process — non-custodial, same model as the web app.
|
|
10
|
+
*/
|
|
11
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
12
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
13
|
+
import { Mppx, evm } from 'mppx/client';
|
|
14
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
const API = (process.env.GLIANA_API_URL ?? 'https://api.glianalabs.com').replace(/\/+$/, '');
|
|
17
|
+
const RAW_KEY = process.env.GLIANA_WALLET_KEY?.trim();
|
|
18
|
+
const WALLET_KEY = RAW_KEY
|
|
19
|
+
? (RAW_KEY.startsWith('0x') ? RAW_KEY : `0x${RAW_KEY}`)
|
|
20
|
+
: undefined;
|
|
21
|
+
const usd = (micro) => `$${(micro / 1_000_000).toFixed(6).replace(/0+$/, '').replace(/\.$/, '')}`;
|
|
22
|
+
async function getJson(path) {
|
|
23
|
+
const r = await fetch(`${API}${path}`, { headers: { accept: 'application/json' } });
|
|
24
|
+
if (!r.ok)
|
|
25
|
+
throw new Error(`GET ${path} → HTTP ${r.status}`);
|
|
26
|
+
return (await r.json());
|
|
27
|
+
}
|
|
28
|
+
const ok = (s) => ({ content: [{ type: 'text', text: s }] });
|
|
29
|
+
const fail = (s) => ({ content: [{ type: 'text', text: s }], isError: true });
|
|
30
|
+
/** Find a renderable media URL in an inference result (gateway or raw provider shape). */
|
|
31
|
+
function mediaUrl(output) {
|
|
32
|
+
const o = output;
|
|
33
|
+
if (!o || typeof o !== 'object')
|
|
34
|
+
return null;
|
|
35
|
+
for (const c of [o, o.result, o.output].filter((x) => !!x && typeof x === 'object')) {
|
|
36
|
+
for (const k of ['url', 'image', 'video', 'audio']) {
|
|
37
|
+
const v = c[k];
|
|
38
|
+
if (typeof v === 'string' && /^https?:\/\//.test(v))
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const server = new McpServer({ name: 'gliana-ai', version: '0.1.0' });
|
|
45
|
+
server.tool('list_models', 'List every GlianaAI model (id, category, provider, per-call price). Free — no payment. Use this to pick a model before get_price/generate.', {}, async () => {
|
|
46
|
+
const { models } = await getJson('/v1/models');
|
|
47
|
+
const byCat = {};
|
|
48
|
+
for (const m of models)
|
|
49
|
+
(byCat[m.category] ??= []).push(m);
|
|
50
|
+
const out = Object.entries(byCat)
|
|
51
|
+
.map(([cat, ms]) => `## ${cat}\n` + ms.map((m) => `- ${m.id} (${m.provider}) — ${m.priceLabel}`).join('\n'))
|
|
52
|
+
.join('\n\n');
|
|
53
|
+
return ok(`${models.length} models on GlianaAI:\n\n${out}`);
|
|
54
|
+
});
|
|
55
|
+
server.tool('get_price', 'Quote the exact cost of one call for a model (optionally with input that affects price, e.g. video duration or TTS character count). Free.', {
|
|
56
|
+
model: z.string().describe('Model id from list_models, e.g. "nano-banana-2" or "veo-3.1-fast".'),
|
|
57
|
+
input: z.record(z.any()).optional().describe('Optional model input that affects price (e.g. { duration: 8 } or { text: "..." }).'),
|
|
58
|
+
}, async ({ model, input }) => {
|
|
59
|
+
const qs = new URLSearchParams({ model });
|
|
60
|
+
if (input)
|
|
61
|
+
for (const [k, v] of Object.entries(input))
|
|
62
|
+
qs.set(k, String(v));
|
|
63
|
+
const p = await getJson(`/v1/price?${qs.toString()}`);
|
|
64
|
+
return ok(`${p.model}: ${usd(p.costMicroUsd)} (${p.units} ${p.unit}${p.units === 1 ? '' : 's'}).`);
|
|
65
|
+
});
|
|
66
|
+
server.tool('get_schema', 'Get a model’s input fields (names, types, which are required, defaults). Use before generate to know what to send. Free.', { model: z.string().describe('Model id from list_models.') }, async ({ model }) => {
|
|
67
|
+
const s = await getJson(`/v1/schema?model=${encodeURIComponent(model)}`);
|
|
68
|
+
return ok(`${s.model} (${s.category})\nrequired: ${s.required.join(', ') || '—'}\n\nfields:\n${JSON.stringify(s.props, null, 2)}`);
|
|
69
|
+
});
|
|
70
|
+
server.tool('generate', 'Run a model and return the result (media URL). PAID: settles the price from your wallet (USDC on Base) via GLIANA_WALLET_KEY. Call get_schema first for the input shape, get_price for the cost.', {
|
|
71
|
+
model: z.string().describe('Model id from list_models.'),
|
|
72
|
+
input: z.record(z.any()).describe('Model input, e.g. { prompt: "a red fox" } or { text: "hello" }. See get_schema.'),
|
|
73
|
+
}, async ({ model, input }) => {
|
|
74
|
+
if (!WALLET_KEY) {
|
|
75
|
+
return fail('generate needs a funded wallet. Set GLIANA_WALLET_KEY (a 0x private key holding USDC on Base) in your MCP client config. ' +
|
|
76
|
+
'Discovery tools (list_models, get_price, get_schema) work without it.');
|
|
77
|
+
}
|
|
78
|
+
let account;
|
|
79
|
+
try {
|
|
80
|
+
account = privateKeyToAccount(WALLET_KEY);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return fail('GLIANA_WALLET_KEY is not a valid private key (expected 0x + 64 hex chars).');
|
|
84
|
+
}
|
|
85
|
+
const body = JSON.stringify({ model, ...input });
|
|
86
|
+
const run = () => {
|
|
87
|
+
// Fresh client per attempt so a stale 402 challenge is never reused.
|
|
88
|
+
const mppx = Mppx.create({ methods: [evm({ account })], polyfill: false });
|
|
89
|
+
return mppx.fetch(`${API}/v1/infer`, { method: 'POST', headers: { 'content-type': 'application/json' }, body });
|
|
90
|
+
};
|
|
91
|
+
let res = await run();
|
|
92
|
+
if (res.status === 402)
|
|
93
|
+
res = await run(); // one retry with a fresh challenge
|
|
94
|
+
if (res.status === 402)
|
|
95
|
+
return fail('Payment did not settle — check the wallet holds USDC on Base and try again.');
|
|
96
|
+
if (res.status === 400) {
|
|
97
|
+
const e = (await res.json().catch(() => ({})));
|
|
98
|
+
return fail(`Missing/invalid input (no charge): ${e.detail ?? 'see get_schema'}`);
|
|
99
|
+
}
|
|
100
|
+
if (!res.ok) {
|
|
101
|
+
const e = (await res.json().catch(() => ({})));
|
|
102
|
+
return fail(e.detail ?? e.error ?? `Request failed (HTTP ${res.status})`);
|
|
103
|
+
}
|
|
104
|
+
const result = (await res.json());
|
|
105
|
+
const url = mediaUrl(result.output);
|
|
106
|
+
const cost = usd(result.costMicroUsd);
|
|
107
|
+
if (url)
|
|
108
|
+
return ok(`${result.model} → ${url}\ncharged ${cost}`);
|
|
109
|
+
return ok(`${result.model} (charged ${cost}):\n${JSON.stringify(result.output, null, 2)}`);
|
|
110
|
+
});
|
|
111
|
+
async function main() {
|
|
112
|
+
const transport = new StdioServerTransport();
|
|
113
|
+
await server.connect(transport);
|
|
114
|
+
// Stderr only — stdout is the MCP transport.
|
|
115
|
+
console.error(`gliana-ai MCP server ready (${API}). generate ${WALLET_KEY ? 'enabled' : 'disabled — set GLIANA_WALLET_KEY'}.`);
|
|
116
|
+
}
|
|
117
|
+
main().catch((err) => {
|
|
118
|
+
console.error('fatal:', err);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gliana-ai-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for GlianaAI — pay-per-call generative AI across 59 models (image, video, music, speech). No signup or API key; each generate is paid per call from your own wallet over MPP / x402.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"gliana-ai-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=18"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"start": "node dist/index.js",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
22
|
+
"mppx": "^0.6.31",
|
|
23
|
+
"viem": "^2.21.0",
|
|
24
|
+
"zod": "^3.23.8"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.5.0",
|
|
28
|
+
"typescript": "^5.6.0"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"mcp",
|
|
32
|
+
"model-context-protocol",
|
|
33
|
+
"ai",
|
|
34
|
+
"image-generation",
|
|
35
|
+
"video-generation",
|
|
36
|
+
"text-to-speech",
|
|
37
|
+
"music-generation",
|
|
38
|
+
"x402",
|
|
39
|
+
"mpp",
|
|
40
|
+
"pay-per-call",
|
|
41
|
+
"gliana"
|
|
42
|
+
],
|
|
43
|
+
"author": "Gliana Labs",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"homepage": "https://ai.glianalabs.com",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/Gliana-Labs/gliana-mcp.git"
|
|
49
|
+
}
|
|
50
|
+
}
|