tunc-mcp 0.1.2 → 0.1.4
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 +56 -7
- package/package.json +1 -1
- package/src/client.js +3 -1
- package/src/index.js +2 -2
package/README.md
CHANGED
|
@@ -36,6 +36,12 @@ lost-update bug — the classic multi-agent failure where a plausible but wrong
|
|
|
36
36
|
number survives — into a loud retry. If you run agent swarms that share state,
|
|
37
37
|
use it.
|
|
38
38
|
|
|
39
|
+
That is Orb, and it is strict — a stale write is refused, nothing is committed.
|
|
40
|
+
**Doc is the opposite on purpose:** `doc_write`'s `base_revision` is a merge
|
|
41
|
+
base, not a lock, so a stale write usually merges into the newer text and
|
|
42
|
+
succeeds. `doc_conflict` appears only when the edits actually collide. Do not
|
|
43
|
+
wrap both in the same "always pass the revision, retry on conflict" helper.
|
|
44
|
+
|
|
39
45
|
## Install
|
|
40
46
|
|
|
41
47
|
```bash
|
|
@@ -128,20 +134,37 @@ await memory.callTool('data_delete', {
|
|
|
128
134
|
if_revision: revision,
|
|
129
135
|
});
|
|
130
136
|
|
|
131
|
-
// Artifacts: store a file, get a private link
|
|
137
|
+
// Artifacts: store a file, get a private link back.
|
|
138
|
+
// art_read is operation list|get; art_write is operation create; art_delete
|
|
139
|
+
// takes an artifact_id and nothing else.
|
|
132
140
|
const files = art();
|
|
133
|
-
await files.callTool('art_write', {
|
|
134
|
-
|
|
141
|
+
const stored = await files.callTool('art_write', {
|
|
142
|
+
operation: 'create',
|
|
143
|
+
name: 'quarterly-report.pdf',
|
|
135
144
|
mime_type: 'application/pdf',
|
|
136
145
|
content_base64: base64Bytes,
|
|
137
146
|
});
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
// the result nests the artifact, keyed by `id`, with a viewer_url to share
|
|
148
|
+
const artifactId = JSON.parse(stored.content[0].text).artifact.id;
|
|
149
|
+
|
|
150
|
+
// Deleting tombstones rather than erases: art_read get still resolves the
|
|
151
|
+
// artifact afterwards, reporting status "deleted", availability "deleted" and
|
|
152
|
+
// viewer_url null, and the content URL answers 401. Do not test for a delete by
|
|
153
|
+
// expecting get to throw — check the status, or that list no longer returns it.
|
|
154
|
+
await files.callTool('art_delete', { artifact_id: artifactId });
|
|
155
|
+
const after = await files.callTool('art_read', { operation: 'get', artifact_id: artifactId });
|
|
156
|
+
console.log(JSON.parse(after.content[0].text).artifact.status); // "deleted"
|
|
157
|
+
|
|
158
|
+
// Documents: build something a human can open and edit.
|
|
159
|
+
// doc_create needs a title AND a markdown body (the schema says only title is
|
|
160
|
+
// required, which is wrong: a doc without markdown gets invalid_arguments).
|
|
161
|
+
// doc_read, doc_write and doc_delete take `doc`.
|
|
140
162
|
const docs = doc();
|
|
141
|
-
await docs.callTool('doc_create', {
|
|
163
|
+
const made = await docs.callTool('doc_create', {
|
|
142
164
|
title: 'Q4 summary',
|
|
143
165
|
markdown: '# Q4\n\nRevenue up 12%.',
|
|
144
166
|
});
|
|
167
|
+
const docId = JSON.parse(made.content[0].text).result.doc.id;
|
|
145
168
|
```
|
|
146
169
|
|
|
147
170
|
Discover what any service offers:
|
|
@@ -158,7 +181,7 @@ export TUNC_API_KEY=tunc_...
|
|
|
158
181
|
|
|
159
182
|
npx tunc-mcp services
|
|
160
183
|
npx tunc-mcp tools --service orb
|
|
161
|
-
npx tunc-mcp call data_read '{"name":"contacts/ada"}' --service orb
|
|
184
|
+
npx tunc-mcp call data_read '{"operation":"get","name":"contacts/ada"}' --service orb
|
|
162
185
|
```
|
|
163
186
|
|
|
164
187
|
## Wire it into an MCP client
|
|
@@ -178,6 +201,30 @@ orchestrator. Point it at the endpoint and pass the key as a bearer header:
|
|
|
178
201
|
}
|
|
179
202
|
```
|
|
180
203
|
|
|
204
|
+
## OAuth instead of a key
|
|
205
|
+
|
|
206
|
+
The servers accept a resource-bound OAuth token as well as an API key. That
|
|
207
|
+
matters for Doc: **`mcp.tuncdoc.com` advertises the same scope names as Orb
|
|
208
|
+
(`orb.read`, `orb.write`, `orb.delete`)**, so a token without a resource binding
|
|
209
|
+
defaults to the Orb audience and Doc rejects it as "not scoped for this service".
|
|
210
|
+
Include the resource in the device-authorization request:
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
resource=https://mcp.tuncdoc.com/mcp
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
That also flips the approval page to the right domain — an unbound grant sends
|
|
217
|
+
people to `tuncorb.com/connect` even when they are authorising Doc.
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
doc({ apiKey: deviceAccessToken });
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Revoking the token (`POST /oauth/revoke`) returns `200`, but the access token
|
|
224
|
+
keeps working until it expires — about 15 minutes. Revocation kills the refresh
|
|
225
|
+
path, not an already-issued access token, so a successful revoke is not proof
|
|
226
|
+
the credential is dead this second.
|
|
227
|
+
|
|
181
228
|
## Agent-to-agent
|
|
182
229
|
|
|
183
230
|
Every service publishes an A2A agent card, so agents that speak A2A can discover
|
|
@@ -226,6 +273,8 @@ service; the message says which.
|
|
|
226
273
|
|
|
227
274
|
`data_update` 接受 `if_revision`。两个智能体写同一条记录时不会互相覆盖:后到的写入收到 `revision_conflict`,重新读取后重试。这把多智能体最常见的“静默丢更新”故障,变成一个可以被发现的错误。
|
|
228
275
|
|
|
276
|
+
注意两个与直觉不同的行为:**Doc** 的 `doc_write` 把 `base_revision` 当作合并基线而不是锁,过期版本通常会被合并而不是报错,只有在编辑内容真正冲突时才返回 `doc_conflict`;**Art** 的删除是标记删除,删除之后 `get` 仍然能读到该记录,但 `status` 为 `deleted`、`viewer_url` 为空、内容链接返回 `401`。
|
|
277
|
+
|
|
229
278
|
```bash
|
|
230
279
|
npm install tunc-mcp
|
|
231
280
|
```
|
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -57,6 +57,8 @@ export function parseBody(contentType, text) {
|
|
|
57
57
|
return last;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
export const VERSION = '0.1.4';
|
|
61
|
+
|
|
60
62
|
export class TuncMcpClient {
|
|
61
63
|
#endpoint;
|
|
62
64
|
#apiKey;
|
|
@@ -142,7 +144,7 @@ export class TuncMcpClient {
|
|
|
142
144
|
const result = await this.#rpc('initialize', {
|
|
143
145
|
protocolVersion: '2025-06-18',
|
|
144
146
|
capabilities: {},
|
|
145
|
-
clientInfo: { name: 'tunc-mcp', version:
|
|
147
|
+
clientInfo: { name: 'tunc-mcp', version: VERSION },
|
|
146
148
|
});
|
|
147
149
|
// Notifications carry no id and expect no body.
|
|
148
150
|
await fetch(this.#endpoint, {
|
package/src/index.js
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
// Tunc Doc — documents, spreadsheets and slide decks, import/export DOCX,
|
|
8
8
|
// XLSX, PPTX and PDF.
|
|
9
9
|
|
|
10
|
-
import { SERVERS, TuncMcpClient, TuncMcpError, parseBody } from './client.js';
|
|
10
|
+
import { SERVERS, TuncMcpClient, TuncMcpError, VERSION, parseBody } from './client.js';
|
|
11
11
|
|
|
12
|
-
export { SERVERS, TuncMcpClient, TuncMcpError, parseBody };
|
|
12
|
+
export { SERVERS, TuncMcpClient, TuncMcpError, VERSION, parseBody };
|
|
13
13
|
|
|
14
14
|
export function orb(options = {}) {
|
|
15
15
|
return new TuncMcpClient({ service: 'orb', ...options });
|