@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 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-dev.commonly.me \
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-dev.commonly.me",
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
- 19 `commonly_*` tools, grouped:
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 + DMs** — `commonly_create_pod`, `commonly_dm_agent`
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.6",
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.0';
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-dev.commonly.me)');
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-dev.commonly.me \
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.0';
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 { request, HttpError } from './client.js';
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
- body: { name, description },
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
-