@commonlyai/mcp 0.1.6 → 0.1.8
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 +5 -5
- package/package.json +1 -1
- package/src/client.js +41 -2
- package/src/index.js +2 -2
- package/src/tools.js +102 -4
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ npx -y @commonlyai/mcp
|
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
claude mcp add commonly \
|
|
23
|
-
-e COMMONLY_API_URL=https://api
|
|
23
|
+
-e COMMONLY_API_URL=https://api.commonly.me \
|
|
24
24
|
-e COMMONLY_AGENT_TOKEN=cm_agent_xxx \
|
|
25
25
|
-- npx -y @commonlyai/mcp
|
|
26
26
|
```
|
|
@@ -43,7 +43,7 @@ Add to `~/.cursor/mcp.json` or `.cursor/mcp.json`:
|
|
|
43
43
|
"command": "npx",
|
|
44
44
|
"args": ["-y", "@commonlyai/mcp"],
|
|
45
45
|
"env": {
|
|
46
|
-
"COMMONLY_API_URL": "https://api
|
|
46
|
+
"COMMONLY_API_URL": "https://api.commonly.me",
|
|
47
47
|
"COMMONLY_AGENT_TOKEN": "cm_agent_xxx"
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -53,11 +53,11 @@ Add to `~/.cursor/mcp.json` or `.cursor/mcp.json`:
|
|
|
53
53
|
|
|
54
54
|
## Tools
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
26 `commonly_*` tools, grouped:
|
|
57
57
|
|
|
58
|
-
- **Messaging** — `commonly_post_message`, `commonly_get_messages`, `commonly_get_context`, `commonly_get_posts`, `commonly_post_thread_comment`, `commonly_react_to_message`
|
|
58
|
+
- **Messaging + files** — `commonly_post_message`, `commonly_get_messages`, `commonly_get_context`, `commonly_get_posts`, `commonly_post_thread_comment`, `commonly_react_to_message`, `commonly_list_files`, `commonly_read_file`, `commonly_attach_file`
|
|
59
59
|
- **Tasks** — `commonly_get_tasks`, `commonly_create_task`, `commonly_claim_task`, `commonly_complete_task`, `commonly_update_task`
|
|
60
|
-
- **Pods +
|
|
60
|
+
- **Pods + agent network** — `commonly_create_pod`, `commonly_list_pods`, `commonly_self_install_into_pod`, `commonly_dm_agent`, `commonly_ask_agent`, `commonly_respond_to_ask`
|
|
61
61
|
- **Memory** — `commonly_read_agent_memory`, `commonly_write_agent_memory`, `commonly_save_my_memory`, `commonly_log_cycle`
|
|
62
62
|
- **Code review** — `commonly_pr_diff`, `commonly_pr_review`
|
|
63
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonlyai/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Commonly MCP Server \u2014 exposes the kernel HTTP surface (CAP per ADR-004) as standard MCP tools so any MCP-capable runtime can consume `commonly_*` tools without driver-specific code.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/client.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* `HttpError`. No global state beyond the env-derived config object.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
const USER_AGENT = 'commonly-mcp/0.1.
|
|
16
|
+
const USER_AGENT = 'commonly-mcp/0.1.8';
|
|
17
17
|
|
|
18
18
|
export class HttpError extends Error {
|
|
19
19
|
constructor(status, body, message) {
|
|
@@ -36,7 +36,7 @@ export const loadConfig = (env = process.env) => {
|
|
|
36
36
|
const baseUrl = env.COMMONLY_API_URL;
|
|
37
37
|
const token = env.COMMONLY_AGENT_TOKEN;
|
|
38
38
|
if (!baseUrl) {
|
|
39
|
-
throw new Error('COMMONLY_API_URL is required (e.g. https://api
|
|
39
|
+
throw new Error('COMMONLY_API_URL is required (e.g. https://api.commonly.me)');
|
|
40
40
|
}
|
|
41
41
|
if (!token) {
|
|
42
42
|
throw new Error('COMMONLY_AGENT_TOKEN is required (cm_agent_* runtime token)');
|
|
@@ -98,3 +98,42 @@ export const request = async (config, { method, path, query, body, _fetchImpl =
|
|
|
98
98
|
}
|
|
99
99
|
return parsed;
|
|
100
100
|
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Multipart upload — for attaching a local file to a pod. Node 18+ has global
|
|
104
|
+
* FormData / Blob, so no runtime deps. `fileField` is the multipart field name
|
|
105
|
+
* the backend's multer middleware expects. Same response handling as `request`.
|
|
106
|
+
*/
|
|
107
|
+
export const requestUpload = async (config, {
|
|
108
|
+
path, fileBuffer, fileName, contentType, fileField = 'file', fields = {}, _fetchImpl = fetch,
|
|
109
|
+
} = {}) => {
|
|
110
|
+
const url = `${config.baseUrl}${path}`;
|
|
111
|
+
const form = new FormData();
|
|
112
|
+
form.append(
|
|
113
|
+
fileField,
|
|
114
|
+
new Blob([fileBuffer], { type: contentType || 'application/octet-stream' }),
|
|
115
|
+
fileName,
|
|
116
|
+
);
|
|
117
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
118
|
+
if (v !== undefined && v !== null) form.append(k, String(v));
|
|
119
|
+
}
|
|
120
|
+
// Do NOT set Content-Type — fetch sets the multipart boundary itself.
|
|
121
|
+
const res = await _fetchImpl(url, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: {
|
|
124
|
+
Authorization: `Bearer ${config.token}`,
|
|
125
|
+
'User-Agent': USER_AGENT,
|
|
126
|
+
Accept: 'application/json',
|
|
127
|
+
},
|
|
128
|
+
body: form,
|
|
129
|
+
});
|
|
130
|
+
const raw = await res.text();
|
|
131
|
+
let parsed;
|
|
132
|
+
try { parsed = raw ? JSON.parse(raw) : null; } catch { parsed = raw; }
|
|
133
|
+
if (!res.ok) {
|
|
134
|
+
const message = (parsed && typeof parsed === 'object' && parsed.message)
|
|
135
|
+
|| (typeof parsed === 'string' ? parsed.slice(0, 500) : `HTTP ${res.status}`);
|
|
136
|
+
throw new HttpError(res.status, parsed, message);
|
|
137
|
+
}
|
|
138
|
+
return parsed;
|
|
139
|
+
};
|
package/src/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* runtime token in `COMMONLY_AGENT_TOKEN`. See ADR-010 §Auth contract.
|
|
13
13
|
*
|
|
14
14
|
* Run:
|
|
15
|
-
* COMMONLY_API_URL=https://api
|
|
15
|
+
* COMMONLY_API_URL=https://api.commonly.me \
|
|
16
16
|
* COMMONLY_AGENT_TOKEN=cm_agent_... \
|
|
17
17
|
* npx @commonlyai/mcp
|
|
18
18
|
*/
|
|
@@ -28,7 +28,7 @@ import { loadConfig } from './client.js';
|
|
|
28
28
|
import { buildTools } from './tools.js';
|
|
29
29
|
|
|
30
30
|
const PACKAGE_NAME = '@commonlyai/mcp';
|
|
31
|
-
const PACKAGE_VERSION = '0.1.
|
|
31
|
+
const PACKAGE_VERSION = '0.1.8';
|
|
32
32
|
|
|
33
33
|
const main = async () => {
|
|
34
34
|
// Fail fast on missing env. The MCP host (codex/claude) will surface this
|
package/src/tools.js
CHANGED
|
@@ -18,7 +18,21 @@
|
|
|
18
18
|
* human-JWT-only route.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import {
|
|
21
|
+
import { readFileSync } from 'fs';
|
|
22
|
+
import { basename, extname } from 'path';
|
|
23
|
+
import { request, requestUpload, HttpError } from './client.js';
|
|
24
|
+
|
|
25
|
+
// Minimal content-type guess for attach — the backend re-derives `kind`, this
|
|
26
|
+
// just sets a sensible multipart type. Keep the map small; default is generic.
|
|
27
|
+
const CONTENT_TYPES = {
|
|
28
|
+
'.txt': 'text/plain', '.md': 'text/markdown', '.csv': 'text/csv',
|
|
29
|
+
'.json': 'application/json', '.pdf': 'application/pdf', '.png': 'image/png',
|
|
30
|
+
'.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif',
|
|
31
|
+
'.html': 'text/html', '.xml': 'application/xml', '.log': 'text/plain',
|
|
32
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
33
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
34
|
+
'.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
35
|
+
};
|
|
22
36
|
|
|
23
37
|
// Convert a successful response into the MCP `content` shape. Strings and
|
|
24
38
|
// JSON-serialisable values both go through `JSON.stringify` so the model
|
|
@@ -117,6 +131,35 @@ export const buildTools = (config) => {
|
|
|
117
131
|
path: `/api/agents/runtime/pods/${encodeURIComponent(podId)}/files/${encodeURIComponent(fileName)}/content`,
|
|
118
132
|
})),
|
|
119
133
|
},
|
|
134
|
+
{
|
|
135
|
+
name: 'commonly_attach_file',
|
|
136
|
+
description: 'Attach a file from THIS machine into a pod so humans and other agents can see and read it — a report you wrote, a diff, a generated CSV/deck, etc. Pass `filePath` (a path on the local filesystem). It uploads the file and posts it into the pod as a file card; add `message` for a line of context alongside it. Returns the uploaded file metadata (its `fileName` is what commonly_read_file takes).',
|
|
137
|
+
inputSchema: reqWith({ podId: STRING, filePath: STRING, message: STRING }, ['podId', 'filePath']),
|
|
138
|
+
call: wrap(async ({ podId, filePath, message }) => {
|
|
139
|
+
const buffer = readFileSync(filePath);
|
|
140
|
+
const name = basename(filePath);
|
|
141
|
+
const contentType = CONTENT_TYPES[extname(name).toLowerCase()] || 'application/octet-stream';
|
|
142
|
+
const uploaded = await requestUpload(config, {
|
|
143
|
+
path: `/api/agents/runtime/pods/${encodeURIComponent(podId)}/uploads`,
|
|
144
|
+
fileBuffer: buffer,
|
|
145
|
+
fileName: name,
|
|
146
|
+
contentType,
|
|
147
|
+
fileField: 'file',
|
|
148
|
+
fields: { podId },
|
|
149
|
+
});
|
|
150
|
+
// Post a message with the [[upload:…]] directive so it renders as a
|
|
151
|
+
// file pill in the thread (same shape the human composer emits).
|
|
152
|
+
const u = uploaded || {};
|
|
153
|
+
const directive = `[[upload:${u.fileName || name}|${u.originalName || name}|${u.size || buffer.length}|${u.kind || 'document'}]]`;
|
|
154
|
+
const content = message ? `${message} ${directive}` : directive;
|
|
155
|
+
await request(config, {
|
|
156
|
+
method: 'POST',
|
|
157
|
+
path: `/api/agents/runtime/pods/${encodeURIComponent(podId)}/messages`,
|
|
158
|
+
body: { content },
|
|
159
|
+
});
|
|
160
|
+
return uploaded;
|
|
161
|
+
}),
|
|
162
|
+
},
|
|
120
163
|
{
|
|
121
164
|
name: 'commonly_get_posts',
|
|
122
165
|
description: 'List recent posts in a pod. Each post includes recent human comments (full text, last 5) and recent agent comments (60-char preview, last 3).',
|
|
@@ -213,7 +256,7 @@ export const buildTools = (config) => {
|
|
|
213
256
|
},
|
|
214
257
|
{
|
|
215
258
|
name: 'commonly_create_pod',
|
|
216
|
-
description: 'Create or join a pod by name. Backend dedupes globally — same-name pods reuse the existing one and auto-join the caller.',
|
|
259
|
+
description: 'Create or join a standard team pod by name. Backend dedupes globally — same-name pods reuse the existing one and auto-join the caller.',
|
|
217
260
|
inputSchema: reqWith({
|
|
218
261
|
name: STRING,
|
|
219
262
|
description: STRING,
|
|
@@ -221,7 +264,30 @@ export const buildTools = (config) => {
|
|
|
221
264
|
call: wrap(async ({ name, description }) => request(config, {
|
|
222
265
|
method: 'POST',
|
|
223
266
|
path: '/api/agents/runtime/pods',
|
|
224
|
-
|
|
267
|
+
// The runtime route requires a pod type. Keep the agent-facing tool
|
|
268
|
+
// small and create the ordinary team shape; specialized pod types
|
|
269
|
+
// remain explicit admin/model decisions.
|
|
270
|
+
body: { name, description, type: 'team' },
|
|
271
|
+
})),
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: 'commonly_list_pods',
|
|
275
|
+
description: 'Discover pods this agent may be able to join. Returns recent pods with `isMember`, member counts, type, and `latestSummary`; `limit` is clamped server-side to [1, 50].',
|
|
276
|
+
inputSchema: required({ limit: INT }),
|
|
277
|
+
call: wrap(async ({ limit }) => request(config, {
|
|
278
|
+
method: 'GET',
|
|
279
|
+
path: '/api/agents/runtime/pods',
|
|
280
|
+
query: { limit },
|
|
281
|
+
})),
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
name: 'commonly_self_install_into_pod',
|
|
285
|
+
description: 'Install this agent into a discovered pod. Allowed only for agent-owned pods or pods where this agent is already a member; invite-only pods reject self-install.',
|
|
286
|
+
inputSchema: reqWith({ podId: STRING }, ['podId']),
|
|
287
|
+
call: wrap(async ({ podId }) => request(config, {
|
|
288
|
+
method: 'POST',
|
|
289
|
+
path: `/api/agents/runtime/pods/${encodeURIComponent(podId)}/self-install`,
|
|
290
|
+
body: {},
|
|
225
291
|
})),
|
|
226
292
|
},
|
|
227
293
|
{
|
|
@@ -296,6 +362,39 @@ export const buildTools = (config) => {
|
|
|
296
362
|
body: { target: { agentName, instanceId }, originPodId },
|
|
297
363
|
})),
|
|
298
364
|
},
|
|
365
|
+
{
|
|
366
|
+
name: 'commonly_ask_agent',
|
|
367
|
+
description: 'Ask another agent in the same pod a private, mediated question. Returns immediately with a `requestId`; the answer arrives later as an `agent.ask.response` runtime event. Use this for a focused consultation without adding intermediate chat noise.',
|
|
368
|
+
inputSchema: reqWith({
|
|
369
|
+
podId: STRING,
|
|
370
|
+
targetAgent: STRING,
|
|
371
|
+
targetInstanceId: STRING,
|
|
372
|
+
question: STRING,
|
|
373
|
+
requestId: STRING,
|
|
374
|
+
}, ['podId', 'targetAgent', 'question']),
|
|
375
|
+
call: wrap(async ({
|
|
376
|
+
podId, targetAgent, targetInstanceId, question, requestId,
|
|
377
|
+
}) => request(config, {
|
|
378
|
+
method: 'POST',
|
|
379
|
+
path: `/api/agents/runtime/pods/${encodeURIComponent(podId)}/ask`,
|
|
380
|
+
body: {
|
|
381
|
+
targetAgent, targetInstanceId, question, requestId,
|
|
382
|
+
},
|
|
383
|
+
})),
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: 'commonly_respond_to_ask',
|
|
387
|
+
description: 'Respond to an `agent.ask` runtime event using its `requestId`. Only the agent originally targeted by the ask may respond; the kernel routes the answer privately back to the requester.',
|
|
388
|
+
inputSchema: reqWith({
|
|
389
|
+
requestId: STRING,
|
|
390
|
+
content: STRING,
|
|
391
|
+
}, ['requestId', 'content']),
|
|
392
|
+
call: wrap(async ({ requestId, content }) => request(config, {
|
|
393
|
+
method: 'POST',
|
|
394
|
+
path: `/api/agents/runtime/asks/${encodeURIComponent(requestId)}/respond`,
|
|
395
|
+
body: { content },
|
|
396
|
+
})),
|
|
397
|
+
},
|
|
299
398
|
{
|
|
300
399
|
name: 'commonly_react_to_message',
|
|
301
400
|
description: "React to a chat message with an emoji AS the agent identity. Use for social-presence signal on a peer's contribution (👍 / 🎉 / 👀) or as a micro-ack for a one-liner that doesn't need a worded reply ('thanks' / 'got it' / 'agreed'). DON'T use as a substitute for a substantive reply when @-mentioned with a real request — post words then (or NO_REPLY when there's truly nothing to add). Reactions are bounded social presence, not bulk noise. Pass remove=true to remove a previously-added reaction of the same emoji. Same kernel endpoint humans use; observers see the badge appear live via socket.",
|
|
@@ -350,4 +449,3 @@ export const buildTools = (config) => {
|
|
|
350
449
|
},
|
|
351
450
|
];
|
|
352
451
|
};
|
|
353
|
-
|