tunc-mcp 0.0.0-stage → 0.1.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 +169 -2
- package/bin/cli.js +96 -0
- package/package.json +56 -4
- package/src/client.js +177 -0
- package/src/index.js +33 -0
package/README.md
CHANGED
|
@@ -1,3 +1,170 @@
|
|
|
1
|
-
#
|
|
1
|
+
# tunc-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Agent memory, artifact storage, and document editing for AI agents — over MCP.**
|
|
4
|
+
|
|
5
|
+
A small, dependency-free MCP (Model Context Protocol) client for three hosted
|
|
6
|
+
services that AI agents and multi-agent systems actually need:
|
|
7
|
+
|
|
8
|
+
| Service | What it is | MCP endpoint |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| **Tunc Orb** | **Agent memory database** — durable records, logs and structured state shared by every agent you run | `https://mcp.tuncorb.com/mcp` |
|
|
11
|
+
| **Tunc Art** | **Artifact storage and private file sharing** — agent-generated files behind short private links | `https://mcp.tuncart.com/mcp` |
|
|
12
|
+
| **Tunc Doc** | **Documents, spreadsheets and slide decks** — import/export DOCX, XLSX, PPTX, PDF | `https://mcp.tuncdoc.com/mcp` |
|
|
13
|
+
|
|
14
|
+
All three are also reachable over **A2A** (agent-to-agent) and plain **REST**, so
|
|
15
|
+
the same account works whether your agent speaks MCP, A2A, or HTTP.
|
|
16
|
+
|
|
17
|
+
## Why this exists
|
|
18
|
+
|
|
19
|
+
Most agent stacks lose state the moment a context window rolls over. Writes from
|
|
20
|
+
two agents at once silently clobber each other. Artifacts end up as files on
|
|
21
|
+
someone's laptop. This gives an agent three boring, reliable primitives instead:
|
|
22
|
+
|
|
23
|
+
- **Memory that survives the conversation.** Records persist across sessions,
|
|
24
|
+
across agents, across models. `data_read` / `data_write` / `data_update` /
|
|
25
|
+
`data_delete`, with revision-guarded writes.
|
|
26
|
+
- **Artifacts with links you can send.** Upload a file, get a short private URL
|
|
27
|
+
back. No bucket configuration.
|
|
28
|
+
- **Documents an agent can actually produce.** Real DOCX, XLSX and PPTX, not
|
|
29
|
+
markdown pretending to be a report.
|
|
30
|
+
|
|
31
|
+
### Concurrency is the interesting part
|
|
32
|
+
|
|
33
|
+
`data_update` accepts `if_revision`. Two agents writing the same record do not
|
|
34
|
+
race: the second gets a `revision_conflict`, re-reads, and retries. That turns a
|
|
35
|
+
lost-update bug — the classic multi-agent failure where a plausible but wrong
|
|
36
|
+
number survives — into a loud retry. If you run agent swarms that share state,
|
|
37
|
+
use it.
|
|
38
|
+
|
|
39
|
+
## Install
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install tunc-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Node 18 or newer. No dependencies. Get an API key at
|
|
46
|
+
<https://tunchub.com/account/keys/new> — you can scope it read-only.
|
|
47
|
+
|
|
48
|
+
## Use
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
import { orb, art, doc } from 'tunc-mcp';
|
|
52
|
+
|
|
53
|
+
const memory = orb(); // reads TUNC_API_KEY
|
|
54
|
+
|
|
55
|
+
// Agent memory: durable records, revision-guarded
|
|
56
|
+
await memory.callTool('data_write', {
|
|
57
|
+
name: 'contacts/ada',
|
|
58
|
+
about: 'Primary contact, Example Ltd',
|
|
59
|
+
value: { email: 'ada@example.com', timezone: 'Europe/London' },
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Artifacts: store a file, get a private link
|
|
63
|
+
const files = art();
|
|
64
|
+
await files.callTool('art_write', {
|
|
65
|
+
title: 'quarterly-report.pdf',
|
|
66
|
+
mime_type: 'application/pdf',
|
|
67
|
+
content_base64: base64Bytes,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Documents: build something a human can open and edit
|
|
71
|
+
const docs = doc();
|
|
72
|
+
await docs.callTool('doc_create', {
|
|
73
|
+
title: 'Q4 summary',
|
|
74
|
+
markdown: '# Q4\n\nRevenue up 12%.',
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Discover what any service offers:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
const tools = await orb().listTools();
|
|
82
|
+
console.log(tools.map((t) => t.name).join(', '));
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## CLI
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
export TUNC_API_KEY=tunc_...
|
|
89
|
+
|
|
90
|
+
npx tunc-mcp services
|
|
91
|
+
npx tunc-mcp tools --service orb
|
|
92
|
+
npx tunc-mcp call data_read '{"name":"contacts/ada"}' --service orb
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Wire it into an MCP client
|
|
96
|
+
|
|
97
|
+
Any MCP-capable host works — Claude Desktop, Cursor, Claude Code, Codex, a custom
|
|
98
|
+
orchestrator. Point it at the endpoint and pass the key as a bearer header:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"tunc-orb": {
|
|
104
|
+
"type": "http",
|
|
105
|
+
"url": "https://mcp.tuncorb.com/mcp",
|
|
106
|
+
"headers": { "Authorization": "Bearer ${TUNC_API_KEY}" }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Agent-to-agent
|
|
113
|
+
|
|
114
|
+
Every service publishes an A2A agent card, so agents that speak A2A can discover
|
|
115
|
+
and call them without a human in the loop:
|
|
116
|
+
|
|
117
|
+
- <https://tuncorb.com/.well-known/agent-card.json>
|
|
118
|
+
- <https://tuncart.com/.well-known/agent-card.json>
|
|
119
|
+
- <https://tuncdoc.com/.well-known/agent-card.json>
|
|
120
|
+
|
|
121
|
+
## What this package does not do
|
|
122
|
+
|
|
123
|
+
It is a client. It does not host anything, and it does not bundle an agent
|
|
124
|
+
runtime. If you want the workspace these services belong to — a multiplayer
|
|
125
|
+
chat where people and agents work together — that is a desktop and mobile app:
|
|
126
|
+
<https://tunchub.com>.
|
|
127
|
+
|
|
128
|
+
## Errors
|
|
129
|
+
|
|
130
|
+
Everything throws `TuncMcpError` with a `status`, a `code` and the server's
|
|
131
|
+
message. A `401` means the key is missing, revoked, or not scoped for that
|
|
132
|
+
service; the message says which.
|
|
133
|
+
|
|
134
|
+
## 中文
|
|
135
|
+
|
|
136
|
+
**面向 AI 智能体的记忆数据库、制品存储与文档编辑 —— 通过 MCP 接入。**
|
|
137
|
+
|
|
138
|
+
`tunc-mcp` 是一个零依赖的 MCP 客户端,连接三个托管服务,解决多智能体系统里最常见的问题:
|
|
139
|
+
|
|
140
|
+
| 服务 | 用途 | 接入点 |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| **Tunc Orb** | **智能体记忆数据库** —— 持久保存记录、日志与结构化状态,可在所有智能体之间共享 | `https://mcp.tuncorb.com/mcp` |
|
|
143
|
+
| **Tunc Art** | **制品存储与文件分享** —— 保存智能体生成的文件,返回私密短链接 | `https://mcp.tuncart.com/mcp` |
|
|
144
|
+
| **Tunc Doc** | **文档、表格与幻灯片** —— 可导入导出 DOCX、XLSX、PPTX、PDF | `https://mcp.tuncdoc.com/mcp` |
|
|
145
|
+
|
|
146
|
+
三个服务同时支持 **MCP**、**A2A**(智能体间通信)与 **REST**,同一个账号即可通用。
|
|
147
|
+
|
|
148
|
+
### 为什么需要它
|
|
149
|
+
|
|
150
|
+
多数智能体框架在上下文窗口滚动之后就会丢失状态;两个智能体同时写入会互相覆盖;生成的制品散落在本地磁盘上。这里提供三个朴素可靠的基础能力:
|
|
151
|
+
|
|
152
|
+
- **能跨越对话的记忆。** 记录跨会话、跨智能体、跨模型持久保存,写入支持 `if_revision` 乐观并发控制。
|
|
153
|
+
- **可分享的制品链接。** 上传文件即得到私密短链接,无需配置对象存储。
|
|
154
|
+
- **智能体能够真正产出的文档。** 真实的 DOCX、XLSX 与 PPTX,而不是伪装成报告的 Markdown。
|
|
155
|
+
|
|
156
|
+
### 并发写入
|
|
157
|
+
|
|
158
|
+
`data_update` 接受 `if_revision`。两个智能体写同一条记录时不会互相覆盖:后到的写入收到 `revision_conflict`,重新读取后重试。这把多智能体最常见的“静默丢更新”故障,变成一个可以被发现的错误。
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm install tunc-mcp
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
API 密钥:<https://tunchub.com/account/keys/new>(可设为只读)。
|
|
165
|
+
|
|
166
|
+
完整的多人协作工作空间(人与多个智能体共享同一实时对话)见 <https://tunchub.com>。
|
|
167
|
+
|
|
168
|
+
## License
|
|
169
|
+
|
|
170
|
+
MIT © TUNC LLC
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// tunc-mcp — inspect and call the Tunc MCP servers from a terminal.
|
|
3
|
+
//
|
|
4
|
+
// export TUNC_API_KEY=tunc_...
|
|
5
|
+
// npx tunc-mcp tools --service orb
|
|
6
|
+
// npx tunc-mcp call data_read '{"name":"contacts/ada"}'
|
|
7
|
+
|
|
8
|
+
import { SERVERS, TuncMcpClient, TuncMcpError } from '../src/client.js';
|
|
9
|
+
|
|
10
|
+
const USAGE = `tunc-mcp — MCP client for Tunc Orb, Tunc Art and Tunc Doc
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
tunc-mcp services
|
|
14
|
+
tunc-mcp tools [--service orb|art|doc] [--json]
|
|
15
|
+
tunc-mcp call <tool> [json-args] [--service orb|art|doc]
|
|
16
|
+
|
|
17
|
+
Environment:
|
|
18
|
+
TUNC_API_KEY required. Create one at https://tunchub.com/account/keys/new
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
function parseArgs(argv) {
|
|
22
|
+
const args = { _: [], service: 'orb', json: false };
|
|
23
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
24
|
+
const arg = argv[i];
|
|
25
|
+
if (arg === '--service') args.service = argv[++i];
|
|
26
|
+
else if (arg === '--json') args.json = true;
|
|
27
|
+
else if (arg === '--help' || arg === '-h') args.help = true;
|
|
28
|
+
else args._.push(arg);
|
|
29
|
+
}
|
|
30
|
+
return args;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
const args = parseArgs(process.argv.slice(2));
|
|
35
|
+
const [command, ...rest] = args._;
|
|
36
|
+
|
|
37
|
+
if (args.help || !command || command === 'services') {
|
|
38
|
+
if (command === 'services') {
|
|
39
|
+
console.log(JSON.stringify(SERVERS, null, 2));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
console.log(USAGE);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const client = new TuncMcpClient({ service: args.service });
|
|
47
|
+
|
|
48
|
+
if (command === 'tools') {
|
|
49
|
+
const tools = await client.listTools();
|
|
50
|
+
if (args.json) {
|
|
51
|
+
console.log(JSON.stringify(tools, null, 2));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.log(`${tools.length} tools on ${args.service} (${client.endpoint})\n`);
|
|
55
|
+
for (const tool of tools) {
|
|
56
|
+
console.log(` ${tool.name}${tool.description ? ` — ${tool.description.split('.')[0]}` : ''}`);
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (command === 'call') {
|
|
62
|
+
const [name, rawArgs] = rest;
|
|
63
|
+
if (!name) {
|
|
64
|
+
console.error('call needs a tool name. Run: tunc-mcp tools');
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let parsed = {};
|
|
69
|
+
if (rawArgs) {
|
|
70
|
+
try {
|
|
71
|
+
parsed = JSON.parse(rawArgs);
|
|
72
|
+
} catch {
|
|
73
|
+
console.error('That is not valid JSON. Quote it, e.g. \'{"name":"x"}\'');
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const result = await client.callTool(name, parsed);
|
|
79
|
+
console.log(JSON.stringify(result, null, 2));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.error(`Unknown command: ${command}\n`);
|
|
84
|
+
console.log(USAGE);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
main().catch((error) => {
|
|
89
|
+
if (error instanceof TuncMcpError) {
|
|
90
|
+
console.error(`tunc-mcp: ${error.message}`);
|
|
91
|
+
if (error.status) console.error(` HTTP ${error.status}`);
|
|
92
|
+
} else {
|
|
93
|
+
console.error(`tunc-mcp: ${error.message}`);
|
|
94
|
+
}
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tunc-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP client for Tunc Orb (agent memory database), Tunc Art (artifact storage and private file sharing) and Tunc Doc (documents, spreadsheets, decks).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"agent-memory",
|
|
9
|
+
"agent-database",
|
|
10
|
+
"memory",
|
|
11
|
+
"agent",
|
|
12
|
+
"agents",
|
|
13
|
+
"multi-agent",
|
|
14
|
+
"multi-agent-orchestration",
|
|
15
|
+
"artifact",
|
|
16
|
+
"artifacts",
|
|
17
|
+
"artifact-storage",
|
|
18
|
+
"file-sharing",
|
|
19
|
+
"private-links",
|
|
20
|
+
"storage",
|
|
21
|
+
"database",
|
|
22
|
+
"records",
|
|
23
|
+
"documents",
|
|
24
|
+
"spreadsheets",
|
|
25
|
+
"slide-decks",
|
|
26
|
+
"a2a",
|
|
27
|
+
"agent-to-agent",
|
|
28
|
+
"ai-agents",
|
|
29
|
+
"llm",
|
|
30
|
+
"tunc"
|
|
31
|
+
],
|
|
32
|
+
"type": "module",
|
|
33
|
+
"main": "./src/index.js",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": "./src/index.js",
|
|
36
|
+
"./client": "./src/client.js"
|
|
37
|
+
},
|
|
38
|
+
"bin": {
|
|
39
|
+
"tunc-mcp": "./bin/cli.js"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"src",
|
|
43
|
+
"bin",
|
|
44
|
+
"README.md"
|
|
45
|
+
],
|
|
46
|
+
"engines": {
|
|
47
|
+
"node": ">=18"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"test": "node --test"
|
|
51
|
+
},
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"homepage": "https://tuncorb.com",
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/TUNC-AI/tunc-mcp.git"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/client.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
// Minimal MCP (Model Context Protocol) client over streamable HTTP.
|
|
2
|
+
//
|
|
3
|
+
// It speaks just enough of the protocol to talk to the Tunc servers: initialize,
|
|
4
|
+
// tools/list and tools/call, with JSON-RPC 2.0 envelopes and event-stream
|
|
5
|
+
// responses. No dependencies; Node 18+ fetch only.
|
|
6
|
+
//
|
|
7
|
+
// Servers:
|
|
8
|
+
// Tunc Orb https://mcp.tuncorb.com/mcp agent memory database
|
|
9
|
+
// Tunc Art https://mcp.tuncart.com/mcp artifact storage, private links
|
|
10
|
+
// Tunc Doc https://mcp.tuncdoc.com/mcp documents, spreadsheets, decks
|
|
11
|
+
|
|
12
|
+
export const SERVERS = Object.freeze({
|
|
13
|
+
orb: 'https://mcp.tuncorb.com/mcp',
|
|
14
|
+
art: 'https://mcp.tuncart.com/mcp',
|
|
15
|
+
doc: 'https://mcp.tuncdoc.com/mcp',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export class TuncMcpError extends Error {
|
|
19
|
+
constructor(message, { code, status, body } = {}) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = 'TuncMcpError';
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.status = status;
|
|
24
|
+
this.body = body;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Pull the JSON-RPC payload out of an event-stream body, or a plain JSON body.
|
|
30
|
+
* Streamable HTTP lets a server answer either way; Tunc answers JSON, other
|
|
31
|
+
* MCP servers may answer text/event-stream, so both are handled.
|
|
32
|
+
*/
|
|
33
|
+
export function parseBody(contentType, text) {
|
|
34
|
+
if (!text) return null;
|
|
35
|
+
if (!/text\/event-stream/i.test(contentType ?? '')) {
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(text);
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let last = null;
|
|
44
|
+
for (const block of text.split(/\r?\n\r?\n/)) {
|
|
45
|
+
const data = block
|
|
46
|
+
.split(/\r?\n/)
|
|
47
|
+
.filter((line) => line.startsWith('data:'))
|
|
48
|
+
.map((line) => line.slice(5).trimStart())
|
|
49
|
+
.join('\n');
|
|
50
|
+
if (!data) continue;
|
|
51
|
+
try {
|
|
52
|
+
last = JSON.parse(data);
|
|
53
|
+
} catch {
|
|
54
|
+
// A partial frame: keep the previous complete one.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return last;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class TuncMcpClient {
|
|
61
|
+
#endpoint;
|
|
62
|
+
#apiKey;
|
|
63
|
+
#sessionId = null;
|
|
64
|
+
#nextId = 1;
|
|
65
|
+
#initialized = false;
|
|
66
|
+
|
|
67
|
+
constructor({ service = 'orb', endpoint, apiKey }) {
|
|
68
|
+
this.#endpoint = endpoint ?? SERVERS[service];
|
|
69
|
+
if (!this.#endpoint) {
|
|
70
|
+
throw new TuncMcpError(`Unknown service "${service}". Use orb, art or doc, or pass an endpoint.`);
|
|
71
|
+
}
|
|
72
|
+
this.#apiKey = apiKey ?? process.env.TUNC_API_KEY;
|
|
73
|
+
if (!this.#apiKey) {
|
|
74
|
+
throw new TuncMcpError(
|
|
75
|
+
'No API key. Pass apiKey or set TUNC_API_KEY. Create one at https://tunchub.com/account/keys/new',
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get endpoint() {
|
|
81
|
+
return this.#endpoint;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async #post(payload) {
|
|
85
|
+
const headers = {
|
|
86
|
+
'content-type': 'application/json',
|
|
87
|
+
accept: 'application/json, text/event-stream',
|
|
88
|
+
authorization: `Bearer ${this.#apiKey}`,
|
|
89
|
+
};
|
|
90
|
+
if (this.#sessionId) headers['mcp-session-id'] = this.#sessionId;
|
|
91
|
+
|
|
92
|
+
const response = await fetch(this.#endpoint, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers,
|
|
95
|
+
body: JSON.stringify(payload),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const session = response.headers.get('mcp-session-id');
|
|
99
|
+
if (session) this.#sessionId = session;
|
|
100
|
+
|
|
101
|
+
const text = await response.text();
|
|
102
|
+
|
|
103
|
+
if (response.status === 401) {
|
|
104
|
+
throw new TuncMcpError(
|
|
105
|
+
'Unauthorized. The API key is missing, revoked, or not scoped for this service.',
|
|
106
|
+
{ status: 401, body: text },
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const body = parseBody(response.headers.get('content-type'), text);
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new TuncMcpError(body?.error?.message ?? `HTTP ${response.status}`, {
|
|
114
|
+
status: response.status,
|
|
115
|
+
code: body?.error?.code,
|
|
116
|
+
body,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (body?.error) {
|
|
121
|
+
throw new TuncMcpError(body.error.message ?? 'JSON-RPC error', {
|
|
122
|
+
code: body.error.code,
|
|
123
|
+
status: response.status,
|
|
124
|
+
body,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return body?.result;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async #rpc(method, params) {
|
|
132
|
+
return this.#post({ jsonrpc: '2.0', id: this.#nextId++, method, params });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async initialize() {
|
|
136
|
+
if (this.#initialized) return this;
|
|
137
|
+
const result = await this.#rpc('initialize', {
|
|
138
|
+
protocolVersion: '2025-06-18',
|
|
139
|
+
capabilities: {},
|
|
140
|
+
clientInfo: { name: 'tunc-mcp', version: '0.1.0' },
|
|
141
|
+
});
|
|
142
|
+
// Notifications carry no id and expect no body.
|
|
143
|
+
await fetch(this.#endpoint, {
|
|
144
|
+
method: 'POST',
|
|
145
|
+
headers: {
|
|
146
|
+
'content-type': 'application/json',
|
|
147
|
+
accept: 'application/json, text/event-stream',
|
|
148
|
+
authorization: `Bearer ${this.#apiKey}`,
|
|
149
|
+
...(this.#sessionId ? { 'mcp-session-id': this.#sessionId } : {}),
|
|
150
|
+
},
|
|
151
|
+
body: JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }),
|
|
152
|
+
}).catch(() => {});
|
|
153
|
+
this.serverInfo = result?.serverInfo ?? null;
|
|
154
|
+
this.capabilities = result?.capabilities ?? null;
|
|
155
|
+
this.#initialized = true;
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async listTools() {
|
|
160
|
+
await this.initialize();
|
|
161
|
+
const result = await this.#rpc('tools/list', {});
|
|
162
|
+
return result?.tools ?? [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async callTool(name, args = {}) {
|
|
166
|
+
await this.initialize();
|
|
167
|
+
return this.#rpc('tools/call', { name, arguments: args });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Convenience: the text content of a tool result, joined. */
|
|
171
|
+
async callText(name, args = {}) {
|
|
172
|
+
const result = await this.callTool(name, args);
|
|
173
|
+
const parts = Array.isArray(result?.content) ? result.content : [];
|
|
174
|
+
const text = parts.filter((p) => p?.type === 'text').map((p) => p.text).join('\n');
|
|
175
|
+
return text || result;
|
|
176
|
+
}
|
|
177
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Convenience layer over the three Tunc MCP servers.
|
|
2
|
+
//
|
|
3
|
+
// Tunc Orb — agent memory database: durable records and logs for agents and
|
|
4
|
+
// agent swarms, revision-guarded so concurrent writers cannot clobber.
|
|
5
|
+
// Tunc Art — artifact storage and private file sharing: agent-generated files
|
|
6
|
+
// with short private links.
|
|
7
|
+
// Tunc Doc — documents, spreadsheets and slide decks, import/export DOCX,
|
|
8
|
+
// XLSX, PPTX and PDF.
|
|
9
|
+
|
|
10
|
+
import { SERVERS, TuncMcpClient, TuncMcpError, parseBody } from './client.js';
|
|
11
|
+
|
|
12
|
+
export { SERVERS, TuncMcpClient, TuncMcpError, parseBody };
|
|
13
|
+
|
|
14
|
+
export function orb(options = {}) {
|
|
15
|
+
return new TuncMcpClient({ service: 'orb', ...options });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function art(options = {}) {
|
|
19
|
+
return new TuncMcpClient({ service: 'art', ...options });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function doc(options = {}) {
|
|
23
|
+
return new TuncMcpClient({ service: 'doc', ...options });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** All three clients from one key. */
|
|
27
|
+
export function connect(apiKey = process.env.TUNC_API_KEY) {
|
|
28
|
+
return {
|
|
29
|
+
orb: orb({ apiKey }),
|
|
30
|
+
art: art({ apiKey }),
|
|
31
|
+
doc: doc({ apiKey }),
|
|
32
|
+
};
|
|
33
|
+
}
|