@ucm/mcp-server 0.1.0 → 0.3.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 +22 -32
- package/dist/client.d.ts +14 -10
- package/dist/client.js +50 -120
- package/dist/index.js +35 -97
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -16,20 +16,7 @@ npx @ucm/mcp-server
|
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
18
18
|
|
|
19
|
-
### 1.
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
node -e "
|
|
23
|
-
import('@noble/ed25519').then(async ed => {
|
|
24
|
-
const priv = ed.utils.randomPrivateKey();
|
|
25
|
-
const pub = await ed.getPublicKeyAsync(priv);
|
|
26
|
-
console.log('UCM_PRIVATE_KEY=' + Buffer.from(priv).toString('hex'));
|
|
27
|
-
console.log('UCM_PUBLIC_KEY=' + Buffer.from(pub).toString('hex'));
|
|
28
|
-
})
|
|
29
|
-
"
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
### 2. Configure your MCP client
|
|
19
|
+
### 1. Configure your MCP client
|
|
33
20
|
|
|
34
21
|
Add to your Claude Desktop `claude_desktop_config.json`:
|
|
35
22
|
|
|
@@ -40,40 +27,43 @@ Add to your Claude Desktop `claude_desktop_config.json`:
|
|
|
40
27
|
"command": "npx",
|
|
41
28
|
"args": ["@ucm/mcp-server"],
|
|
42
29
|
"env": {
|
|
43
|
-
"
|
|
44
|
-
"UCM_PRIVATE_KEY": "<your-private-key-hex>",
|
|
45
|
-
"UCM_PUBLIC_KEY": "<your-public-key-hex>"
|
|
30
|
+
"UCM_API_KEY": "<your-api-key>"
|
|
46
31
|
}
|
|
47
32
|
}
|
|
48
33
|
}
|
|
49
34
|
}
|
|
50
35
|
```
|
|
51
36
|
|
|
52
|
-
|
|
37
|
+
Don't have an API key? No problem — your agent can use `ucm_register` to self-register and get one automatically.
|
|
38
|
+
|
|
39
|
+
### 2. Use via your agent
|
|
53
40
|
|
|
54
|
-
Your agent now has access to
|
|
41
|
+
Your agent now has access to 7 tools:
|
|
55
42
|
|
|
56
43
|
| Tool | Description |
|
|
57
44
|
|------|-------------|
|
|
58
45
|
| `ucm_discover` | Search marketplace by natural language |
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
61
|
-
| `ucm_balance` | Check budget remaining |
|
|
46
|
+
| `ucm_call` | Call an API service (buy + execute + auto-refund on failure) |
|
|
47
|
+
| `ucm_balance` | Check credit balance |
|
|
62
48
|
| `ucm_history` | View transaction history |
|
|
63
49
|
| `ucm_service_info` | Get service details |
|
|
64
|
-
| `ucm_register` | Self-register as agent, get API key |
|
|
65
|
-
| `
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
50
|
+
| `ucm_register` | Self-register as agent, get API key + $1.00 credits |
|
|
51
|
+
| `ucm_list_services` | Browse the full service catalog |
|
|
52
|
+
|
|
53
|
+
### 3. Typical flow
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
Agent: ucm_register(name: "my-agent") → gets API key + $1.00 credits
|
|
57
|
+
Agent: ucm_discover(need: "web search") → finds ucm/web-search
|
|
58
|
+
Agent: ucm_call(service_id: "ucm/web-search", endpoint: "search", body: {query: "..."})
|
|
59
|
+
```
|
|
69
60
|
|
|
70
61
|
## Environment Variables
|
|
71
62
|
|
|
72
63
|
| Variable | Required | Default | Description |
|
|
73
64
|
|----------|----------|---------|-------------|
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `UCM_REGISTRY_URL` | No | `http://localhost:3000` | UCM Registry URL |
|
|
65
|
+
| `UCM_API_KEY` | No | — | Agent API key (`ucm_key_*`). Can also be obtained via `ucm_register`. |
|
|
66
|
+
| `UCM_REGISTRY_URL` | No | `https://registry.ucm.ai` | UCM Registry URL |
|
|
77
67
|
|
|
78
68
|
## How It Works
|
|
79
69
|
|
|
@@ -83,8 +73,8 @@ Agent → MCP Client → UCM MCP Server → UCM Registry → Provider APIs
|
|
|
83
73
|
|
|
84
74
|
1. Your agent describes what it needs (e.g., "I need a web search API")
|
|
85
75
|
2. The MCP server searches the UCM marketplace
|
|
86
|
-
3. Agent
|
|
87
|
-
4.
|
|
76
|
+
3. Agent calls the service — purchase and execution happen atomically
|
|
77
|
+
4. If the upstream API fails, credits are automatically refunded
|
|
88
78
|
|
|
89
79
|
## License
|
|
90
80
|
|
package/dist/client.d.ts
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UCM Registry HTTP client with API key auth.
|
|
3
|
+
*/
|
|
1
4
|
export interface UCMConfig {
|
|
2
5
|
registryUrl: string;
|
|
3
|
-
|
|
4
|
-
agentPublicKey: string;
|
|
6
|
+
apiKey?: string;
|
|
5
7
|
}
|
|
6
8
|
export declare class UCMClient {
|
|
7
9
|
private config;
|
|
8
10
|
constructor(config: UCMConfig);
|
|
9
|
-
|
|
11
|
+
setApiKey(key: string): void;
|
|
12
|
+
private bearerFetch;
|
|
10
13
|
private unsignedFetch;
|
|
11
|
-
private
|
|
14
|
+
private handleResponse;
|
|
12
15
|
discover(need: string, options?: {
|
|
13
16
|
tags?: string[];
|
|
14
17
|
max_price?: string;
|
|
15
18
|
min_reputation?: number;
|
|
16
19
|
limit?: number;
|
|
17
20
|
}): Promise<unknown>;
|
|
21
|
+
call(serviceId: string, endpoint: string, body?: Record<string, unknown>): Promise<unknown>;
|
|
18
22
|
buy(serviceId: string, plan?: string): Promise<unknown>;
|
|
19
23
|
balance(): Promise<unknown>;
|
|
20
24
|
history(limit?: number): Promise<unknown>;
|
|
21
|
-
call(serviceId: string, method: string, params: Record<string, unknown>): Promise<any>;
|
|
22
25
|
getService(serviceId: string): Promise<unknown>;
|
|
23
|
-
register(name: string, description?: string): Promise<unknown>;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
register(name: string, description?: string, ref?: string): Promise<unknown>;
|
|
27
|
+
listServices(options?: {
|
|
28
|
+
since?: string;
|
|
29
|
+
tags?: string;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}): Promise<unknown>;
|
|
28
32
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,91 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* UCM Registry HTTP client with
|
|
2
|
+
* UCM Registry HTTP client with API key auth.
|
|
3
3
|
*/
|
|
4
|
-
import * as ed from '@noble/ed25519';
|
|
5
|
-
import { createHash } from 'crypto';
|
|
6
|
-
// Setup sha512 for noble/ed25519 v2
|
|
7
|
-
ed.etc.sha512Sync = (...m) => {
|
|
8
|
-
const h = createHash('sha512');
|
|
9
|
-
for (const msg of m)
|
|
10
|
-
h.update(msg);
|
|
11
|
-
return new Uint8Array(h.digest());
|
|
12
|
-
};
|
|
13
|
-
function hexToBytes(hex) {
|
|
14
|
-
const bytes = new Uint8Array(hex.length / 2);
|
|
15
|
-
for (let i = 0; i < hex.length; i += 2) {
|
|
16
|
-
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
|
|
17
|
-
}
|
|
18
|
-
return bytes;
|
|
19
|
-
}
|
|
20
|
-
function isHex(value) {
|
|
21
|
-
return value.length % 2 === 0 && /^[0-9a-fA-F]+$/.test(value);
|
|
22
|
-
}
|
|
23
|
-
function decodeKey(key) {
|
|
24
|
-
if (isHex(key)) {
|
|
25
|
-
return hexToBytes(key);
|
|
26
|
-
}
|
|
27
|
-
return new Uint8Array(Buffer.from(key, 'base64url'));
|
|
28
|
-
}
|
|
29
|
-
function sha256Hex(data) {
|
|
30
|
-
return createHash('sha256').update(data).digest('hex');
|
|
31
|
-
}
|
|
32
|
-
function isPlainObject(value) {
|
|
33
|
-
return Object.prototype.toString.call(value) === '[object Object]';
|
|
34
|
-
}
|
|
35
|
-
function canonicalize(value) {
|
|
36
|
-
if (Array.isArray(value)) {
|
|
37
|
-
return value.map(canonicalize);
|
|
38
|
-
}
|
|
39
|
-
if (isPlainObject(value)) {
|
|
40
|
-
const sortedKeys = Object.keys(value).sort();
|
|
41
|
-
const result = {};
|
|
42
|
-
for (const key of sortedKeys) {
|
|
43
|
-
result[key] = canonicalize(value[key]);
|
|
44
|
-
}
|
|
45
|
-
return result;
|
|
46
|
-
}
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
function canonicalJson(obj) {
|
|
50
|
-
return JSON.stringify(canonicalize(obj));
|
|
51
|
-
}
|
|
52
4
|
export class UCMClient {
|
|
53
5
|
config;
|
|
54
6
|
constructor(config) {
|
|
55
7
|
this.config = config;
|
|
56
8
|
}
|
|
57
|
-
|
|
9
|
+
setApiKey(key) {
|
|
10
|
+
this.config.apiKey = key;
|
|
11
|
+
}
|
|
12
|
+
async bearerFetch(path, method, body) {
|
|
13
|
+
if (!this.config.apiKey) {
|
|
14
|
+
throw new Error('No API key configured. Use ucm_register to get one, or set UCM_API_KEY.');
|
|
15
|
+
}
|
|
58
16
|
const url = new URL(path, this.config.registryUrl);
|
|
59
|
-
const bodyStr = body ? JSON.stringify(body) : '';
|
|
60
|
-
const timestamp = Date.now();
|
|
61
|
-
const nonce = crypto.randomUUID();
|
|
62
|
-
const pathWithQuery = `${url.pathname}${url.search}`;
|
|
63
|
-
const payload = canonicalJson({
|
|
64
|
-
body_hash: sha256Hex(bodyStr || ''),
|
|
65
|
-
method,
|
|
66
|
-
nonce,
|
|
67
|
-
path: pathWithQuery,
|
|
68
|
-
timestamp,
|
|
69
|
-
});
|
|
70
|
-
const signature = await this.sign(sha256Hex(payload));
|
|
71
|
-
const headers = {
|
|
72
|
-
'Content-Type': 'application/json',
|
|
73
|
-
'X-ACP-Pubkey': this.config.agentPublicKey,
|
|
74
|
-
'X-ACP-Timestamp': String(timestamp),
|
|
75
|
-
'X-ACP-Nonce': nonce,
|
|
76
|
-
'X-ACP-Signature': signature,
|
|
77
|
-
};
|
|
78
17
|
const res = await fetch(url, {
|
|
79
18
|
method,
|
|
80
|
-
headers
|
|
81
|
-
|
|
19
|
+
headers: {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
22
|
+
},
|
|
23
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
82
24
|
});
|
|
83
|
-
|
|
84
|
-
if (!res.ok) {
|
|
85
|
-
const err = data.error;
|
|
86
|
-
throw new Error(`${err?.code ?? res.status}: ${err?.message ?? 'Request failed'}`);
|
|
87
|
-
}
|
|
88
|
-
return data;
|
|
25
|
+
return this.handleResponse(res);
|
|
89
26
|
}
|
|
90
27
|
async unsignedFetch(path, method, body) {
|
|
91
28
|
const url = new URL(path, this.config.registryUrl);
|
|
@@ -94,67 +31,60 @@ export class UCMClient {
|
|
|
94
31
|
headers: { 'Content-Type': 'application/json' },
|
|
95
32
|
body: body ? JSON.stringify(body) : undefined,
|
|
96
33
|
});
|
|
34
|
+
return this.handleResponse(res);
|
|
35
|
+
}
|
|
36
|
+
async handleResponse(res) {
|
|
97
37
|
const data = await res.json();
|
|
98
38
|
if (!res.ok) {
|
|
99
39
|
const err = data.error;
|
|
100
|
-
|
|
40
|
+
const base = `${err?.code ?? res.status}: ${err?.message ?? 'Request failed'}`;
|
|
41
|
+
const extras = [];
|
|
42
|
+
if (err?.claim_url)
|
|
43
|
+
extras.push(`Claim URL: ${err.claim_url}`);
|
|
44
|
+
if (err?.claim_instructions)
|
|
45
|
+
extras.push(`Instructions: ${err.claim_instructions}`);
|
|
46
|
+
if (err?.balance)
|
|
47
|
+
extras.push(`Current balance: ${err.balance} ${err.currency ?? ''}`);
|
|
48
|
+
throw new Error(extras.length > 0 ? `${base}\n${extras.join('\n')}` : base);
|
|
101
49
|
}
|
|
102
50
|
return data;
|
|
103
51
|
}
|
|
104
|
-
async sign(message) {
|
|
105
|
-
const msgBytes = new TextEncoder().encode(message);
|
|
106
|
-
const sig = await ed.signAsync(msgBytes, decodeKey(this.config.agentPrivateKey));
|
|
107
|
-
return Buffer.from(sig).toString('base64url');
|
|
108
|
-
}
|
|
109
52
|
async discover(need, options) {
|
|
110
53
|
return this.unsignedFetch('/v1/discover', 'POST', { need, ...options });
|
|
111
54
|
}
|
|
55
|
+
async call(serviceId, endpoint, body) {
|
|
56
|
+
return this.bearerFetch('/v1/call', 'POST', {
|
|
57
|
+
service_id: serviceId,
|
|
58
|
+
endpoint,
|
|
59
|
+
...(body && Object.keys(body).length > 0 ? body : {}),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
112
62
|
async buy(serviceId, plan) {
|
|
113
|
-
return this.
|
|
63
|
+
return this.bearerFetch('/v1/buy', 'POST', { service_id: serviceId, plan: plan ?? 'per-call' });
|
|
114
64
|
}
|
|
115
65
|
async balance() {
|
|
116
|
-
return this.
|
|
66
|
+
return this.bearerFetch('/v1/balance', 'GET');
|
|
117
67
|
}
|
|
118
68
|
async history(limit) {
|
|
119
69
|
const path = limit ? `/v1/history?limit=${limit}` : '/v1/history';
|
|
120
|
-
return this.
|
|
121
|
-
}
|
|
122
|
-
async call(serviceId, method, params) {
|
|
123
|
-
// 1. Buy to get a token
|
|
124
|
-
const buyResult = await this.buy(serviceId);
|
|
125
|
-
const token = buyResult.access_token;
|
|
126
|
-
const endpoint = buyResult.service_endpoint;
|
|
127
|
-
// 2. Call the provider API
|
|
128
|
-
const res = await fetch(`${endpoint}${method}`, {
|
|
129
|
-
method: 'POST',
|
|
130
|
-
headers: {
|
|
131
|
-
'Content-Type': 'application/json',
|
|
132
|
-
'Authorization': `Bearer ${token}`,
|
|
133
|
-
},
|
|
134
|
-
body: JSON.stringify(params),
|
|
135
|
-
});
|
|
136
|
-
if (!res.ok) {
|
|
137
|
-
const text = await res.text();
|
|
138
|
-
throw new Error(`Provider API error ${res.status}: ${text}`);
|
|
139
|
-
}
|
|
140
|
-
return res.json();
|
|
70
|
+
return this.bearerFetch(path, 'GET');
|
|
141
71
|
}
|
|
142
72
|
async getService(serviceId) {
|
|
143
73
|
return this.unsignedFetch(`/v1/services/${serviceId}`, 'GET');
|
|
144
74
|
}
|
|
145
|
-
async register(name, description) {
|
|
146
|
-
return this.unsignedFetch('/v1/agents/register', 'POST', { name, description });
|
|
147
|
-
}
|
|
148
|
-
async
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
return this.unsignedFetch(
|
|
75
|
+
async register(name, description, ref) {
|
|
76
|
+
return this.unsignedFetch('/v1/agents/register', 'POST', { name, description, ref });
|
|
77
|
+
}
|
|
78
|
+
async listServices(options) {
|
|
79
|
+
const params = new URLSearchParams();
|
|
80
|
+
if (options?.since)
|
|
81
|
+
params.set('since', options.since);
|
|
82
|
+
if (options?.tags)
|
|
83
|
+
params.set('tags', options.tags);
|
|
84
|
+
if (options?.limit)
|
|
85
|
+
params.set('limit', String(options.limit));
|
|
86
|
+
const query = params.toString();
|
|
87
|
+
const path = query ? `/v1/services?${query}` : '/v1/services';
|
|
88
|
+
return this.unsignedFetch(path, 'GET');
|
|
159
89
|
}
|
|
160
90
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* UCM MCP Server
|
|
3
|
+
* UCM MCP Server v0.3.0
|
|
4
4
|
*
|
|
5
5
|
* Exposes UCM as MCP tools so any compatible agent (Claude, GPT, etc.)
|
|
6
|
-
* can discover
|
|
6
|
+
* can discover and use API services autonomously.
|
|
7
7
|
*
|
|
8
8
|
* Config via env vars:
|
|
9
|
-
* UCM_REGISTRY_URL — Registry base URL (default:
|
|
10
|
-
*
|
|
11
|
-
* UCM_PUBLIC_KEY — Agent Ed25519 public key (hex)
|
|
9
|
+
* UCM_REGISTRY_URL — Registry base URL (default: https://registry.ucm.ai)
|
|
10
|
+
* UCM_API_KEY — Agent API key (ucm_key_*). Optional — use ucm_register to get one.
|
|
12
11
|
*/
|
|
13
12
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
14
13
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
15
14
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
16
15
|
import { UCMClient } from './client.js';
|
|
17
|
-
const registryUrl = process.env.UCM_REGISTRY_URL ?? '
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
console.error('Error: UCM_PRIVATE_KEY and UCM_PUBLIC_KEY env vars are required.');
|
|
22
|
-
process.exit(1);
|
|
23
|
-
}
|
|
24
|
-
const client = new UCMClient({ registryUrl, agentPrivateKey: privateKey, agentPublicKey: publicKey });
|
|
25
|
-
const server = new Server({ name: 'ucm', version: '0.1.0' }, { capabilities: { tools: {} } });
|
|
16
|
+
const registryUrl = process.env.UCM_REGISTRY_URL ?? 'https://registry.ucm.ai';
|
|
17
|
+
const apiKey = process.env.UCM_API_KEY ?? '';
|
|
18
|
+
let client = new UCMClient({ registryUrl, apiKey: apiKey || undefined });
|
|
19
|
+
const server = new Server({ name: 'ucm', version: '0.3.0' }, { capabilities: { tools: {} } });
|
|
26
20
|
// --- tools/list ---
|
|
27
21
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
28
22
|
tools: [
|
|
29
23
|
{
|
|
30
24
|
name: 'ucm_discover',
|
|
31
|
-
description: 'Search UCM marketplace for API services
|
|
25
|
+
description: 'Search UCM marketplace for API services when you need capabilities beyond your built-in tools — web search, code execution, translation, image generation, data processing, etc. New services are added regularly. Returns ranked results with pricing.',
|
|
32
26
|
inputSchema: {
|
|
33
27
|
type: 'object',
|
|
34
28
|
properties: {
|
|
@@ -40,34 +34,22 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
40
34
|
required: ['need'],
|
|
41
35
|
},
|
|
42
36
|
},
|
|
43
|
-
{
|
|
44
|
-
name: 'ucm_buy',
|
|
45
|
-
description: 'Purchase access to an API service. Returns an access token and the service endpoint. Charges are deducted from your budget.',
|
|
46
|
-
inputSchema: {
|
|
47
|
-
type: 'object',
|
|
48
|
-
properties: {
|
|
49
|
-
service_id: { type: 'string', description: 'Service ID, e.g. "acme/web-search"' },
|
|
50
|
-
plan: { type: 'string', description: 'Pricing plan: "per-call" (default) or "subscription"' },
|
|
51
|
-
},
|
|
52
|
-
required: ['service_id'],
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
37
|
{
|
|
56
38
|
name: 'ucm_call',
|
|
57
|
-
description: '
|
|
39
|
+
description: 'Call an API service in one step. Purchases access, executes the call, and returns the result. If the upstream API fails, credits are automatically refunded.',
|
|
58
40
|
inputSchema: {
|
|
59
41
|
type: 'object',
|
|
60
42
|
properties: {
|
|
61
|
-
service_id: { type: 'string', description: 'Service ID
|
|
62
|
-
|
|
63
|
-
|
|
43
|
+
service_id: { type: 'string', description: 'Service ID, e.g. "ucm/web-search"' },
|
|
44
|
+
endpoint: { type: 'string', description: 'Endpoint name, e.g. "search", "generate", "execute"' },
|
|
45
|
+
body: { type: 'object', description: 'Parameters for the API call (optional for endpoints with no required params)' },
|
|
64
46
|
},
|
|
65
|
-
required: ['service_id', '
|
|
47
|
+
required: ['service_id', 'endpoint'],
|
|
66
48
|
},
|
|
67
49
|
},
|
|
68
50
|
{
|
|
69
51
|
name: 'ucm_balance',
|
|
70
|
-
description: 'Check your current UCM
|
|
52
|
+
description: 'Check your current UCM credit balance and spending. Returns credits (balance, lifetime_added, lifetime_spent) and usage counters.',
|
|
71
53
|
inputSchema: { type: 'object', properties: {} },
|
|
72
54
|
},
|
|
73
55
|
{
|
|
@@ -93,60 +75,27 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
93
75
|
},
|
|
94
76
|
{
|
|
95
77
|
name: 'ucm_register',
|
|
96
|
-
description: 'Self-register as a new agent on UCM. Returns an agent ID and API key for
|
|
78
|
+
description: 'Self-register as a new agent on UCM. Returns an agent ID, API key, and $1.00 bonus credits. The API key is automatically saved for this session. Supports optional referral code for extra credits.',
|
|
97
79
|
inputSchema: {
|
|
98
80
|
type: 'object',
|
|
99
81
|
properties: {
|
|
100
82
|
name: { type: 'string', description: 'Agent name (1-255 characters)' },
|
|
101
83
|
description: { type: 'string', description: 'Optional agent description' },
|
|
84
|
+
ref: { type: 'string', description: 'Optional referral code' },
|
|
102
85
|
},
|
|
103
86
|
required: ['name'],
|
|
104
87
|
},
|
|
105
88
|
},
|
|
106
89
|
{
|
|
107
|
-
name: '
|
|
108
|
-
description: '
|
|
109
|
-
inputSchema: {
|
|
110
|
-
type: 'object',
|
|
111
|
-
properties: {
|
|
112
|
-
descriptor: { type: 'object', description: 'Full ACP service descriptor object with acp_version, service_id, name, provider, endpoints, pricing, sla, etc.' },
|
|
113
|
-
},
|
|
114
|
-
required: ['descriptor'],
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: 'ucm_delete_service',
|
|
119
|
-
description: 'Remove (delist) a service from the UCM marketplace. Only the service provider can delete it.',
|
|
120
|
-
inputSchema: {
|
|
121
|
-
type: 'object',
|
|
122
|
-
properties: {
|
|
123
|
-
service_id: { type: 'string', description: 'Service ID to delete' },
|
|
124
|
-
},
|
|
125
|
-
required: ['service_id'],
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: 'ucm_authorize',
|
|
130
|
-
description: 'Create a budget authorization for an agent. Sets per-transaction, daily, and monthly spending limits.',
|
|
90
|
+
name: 'ucm_list_services',
|
|
91
|
+
description: 'Browse the full UCM service catalog, or check for newly added services since a given date.',
|
|
131
92
|
inputSchema: {
|
|
132
93
|
type: 'object',
|
|
133
94
|
properties: {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
95
|
+
since: { type: 'string', description: 'ISO 8601 date — only show services added/updated after this date' },
|
|
96
|
+
tags: { type: 'string', description: 'Comma-separated tag filter' },
|
|
97
|
+
limit: { type: 'number', description: 'Max results (default 50, max 100)' },
|
|
137
98
|
},
|
|
138
|
-
required: ['agent', 'rules', 'operator'],
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
name: 'ucm_verify_token',
|
|
143
|
-
description: 'Verify an ACP access token. Returns token validity and payload details.',
|
|
144
|
-
inputSchema: {
|
|
145
|
-
type: 'object',
|
|
146
|
-
properties: {
|
|
147
|
-
token: { type: 'string', description: 'Access token starting with "acp_sk_"' },
|
|
148
|
-
},
|
|
149
|
-
required: ['token'],
|
|
150
99
|
},
|
|
151
100
|
},
|
|
152
101
|
],
|
|
@@ -164,11 +113,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
164
113
|
limit: args.limit,
|
|
165
114
|
});
|
|
166
115
|
break;
|
|
167
|
-
case 'ucm_buy':
|
|
168
|
-
result = await client.buy(args.service_id, args.plan);
|
|
169
|
-
break;
|
|
170
116
|
case 'ucm_call':
|
|
171
|
-
result = await client.call(args.service_id, args.
|
|
117
|
+
result = await client.call(args.service_id, args.endpoint, args.body);
|
|
172
118
|
break;
|
|
173
119
|
case 'ucm_balance':
|
|
174
120
|
result = await client.balance();
|
|
@@ -179,29 +125,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
179
125
|
case 'ucm_service_info':
|
|
180
126
|
result = await client.getService(args.service_id);
|
|
181
127
|
break;
|
|
182
|
-
case 'ucm_register':
|
|
183
|
-
result = await client.register(args.name, args.description);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
128
|
+
case 'ucm_register': {
|
|
129
|
+
result = await client.register(args.name, args.description, args.ref);
|
|
130
|
+
const apiKey = result.api_key;
|
|
131
|
+
if (apiKey) {
|
|
132
|
+
client.setApiKey(apiKey);
|
|
133
|
+
}
|
|
187
134
|
break;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
rules: args.rules,
|
|
195
|
-
operator: args.operator,
|
|
196
|
-
acp_version: '0.1',
|
|
197
|
-
type: 'budget_authorization',
|
|
198
|
-
created_at: new Date().toISOString(),
|
|
199
|
-
signature: 'mcp-tool',
|
|
135
|
+
}
|
|
136
|
+
case 'ucm_list_services':
|
|
137
|
+
result = await client.listServices({
|
|
138
|
+
since: args.since,
|
|
139
|
+
tags: args.tags,
|
|
140
|
+
limit: args.limit,
|
|
200
141
|
});
|
|
201
142
|
break;
|
|
202
|
-
case 'ucm_verify_token':
|
|
203
|
-
result = await client.verifyToken(args.token);
|
|
204
|
-
break;
|
|
205
143
|
default:
|
|
206
144
|
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
207
145
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ucm/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for UCM — Agent-Native API Marketplace. Gives AI agents access to discover, purchase, and use API services.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,8 +39,7 @@
|
|
|
39
39
|
"prepublishOnly": "npm run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
43
|
-
"@noble/ed25519": "^2.2.0"
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.12.0"
|
|
44
43
|
},
|
|
45
44
|
"devDependencies": {
|
|
46
45
|
"tsx": "^4.19.0",
|