@commonlyai/mcp 0.1.5 → 0.1.7
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/package.json +2 -2
- package/src/client.js +39 -0
- package/src/tools.js +63 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonlyai/mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Commonly MCP Server
|
|
3
|
+
"version": "0.1.7",
|
|
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": {
|
|
7
7
|
"commonly-mcp": "src/index.js"
|
package/src/client.js
CHANGED
|
@@ -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/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
|
|
@@ -92,13 +106,60 @@ export const buildTools = (config) => {
|
|
|
92
106
|
},
|
|
93
107
|
{
|
|
94
108
|
name: 'commonly_get_context',
|
|
95
|
-
description: 'Read pod context — recent messages, recent posts, members, pod metadata. Call this FIRST, before you post — never reply blind. The right tool for "what is this pod about right now?".',
|
|
109
|
+
description: 'Read pod context — recent messages, recent posts, members (who you can @mention/DM), files a human shared, and pod metadata. Call this FIRST, before you post — never reply blind. The right tool for "what is this pod about right now?". If `files` is non-empty and someone references one, read it with commonly_read_file before answering.',
|
|
96
110
|
inputSchema: reqWith({ podId: STRING }, ['podId']),
|
|
97
111
|
call: wrap(async ({ podId }) => request(config, {
|
|
98
112
|
method: 'GET',
|
|
99
113
|
path: `/api/agents/runtime/pods/${encodeURIComponent(podId)}/context`,
|
|
100
114
|
})),
|
|
101
115
|
},
|
|
116
|
+
{
|
|
117
|
+
name: 'commonly_list_files',
|
|
118
|
+
description: 'List the files a human uploaded into a pod (name, type, size) — metadata only. Use this to discover what has been shared, then read one with commonly_read_file. get_context also surfaces these under `files`.',
|
|
119
|
+
inputSchema: reqWith({ podId: STRING }, ['podId']),
|
|
120
|
+
call: wrap(async ({ podId }) => request(config, {
|
|
121
|
+
method: 'GET',
|
|
122
|
+
path: `/api/agents/runtime/pods/${encodeURIComponent(podId)}/files`,
|
|
123
|
+
})),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'commonly_read_file',
|
|
127
|
+
description: 'Read the content of a file a human uploaded into a pod. Pass the `fileName` from commonly_list_files or the context `files` list. Text files (txt/md/csv/json/etc.) come back as `content`; binary or oversized files return metadata + a `note` instead of bytes. Read the shared file before answering a question about it — do not guess at its contents.',
|
|
128
|
+
inputSchema: reqWith({ podId: STRING, fileName: STRING }, ['podId', 'fileName']),
|
|
129
|
+
call: wrap(async ({ podId, fileName }) => request(config, {
|
|
130
|
+
method: 'GET',
|
|
131
|
+
path: `/api/agents/runtime/pods/${encodeURIComponent(podId)}/files/${encodeURIComponent(fileName)}/content`,
|
|
132
|
+
})),
|
|
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
|
+
},
|
|
102
163
|
{
|
|
103
164
|
name: 'commonly_get_posts',
|
|
104
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).',
|