@aigentyc/mcp 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/AGENTS.md +197 -0
- package/LICENSE +21 -0
- package/README.md +112 -0
- package/context7.json +3 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +289 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +10 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +70 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +28 -0
- package/dist/errors.js.map +1 -0
- package/dist/fs-safety.d.ts +29 -0
- package/dist/fs-safety.d.ts.map +1 -0
- package/dist/fs-safety.js +107 -0
- package/dist/fs-safety.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/llms-full.txt +659 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +57 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/analytics.d.ts +4 -0
- package/dist/tools/analytics.d.ts.map +1 -0
- package/dist/tools/analytics.js +82 -0
- package/dist/tools/analytics.js.map +1 -0
- package/dist/tools/backups.d.ts +4 -0
- package/dist/tools/backups.d.ts.map +1 -0
- package/dist/tools/backups.js +99 -0
- package/dist/tools/backups.js.map +1 -0
- package/dist/tools/config.d.ts +4 -0
- package/dist/tools/config.d.ts.map +1 -0
- package/dist/tools/config.js +84 -0
- package/dist/tools/config.js.map +1 -0
- package/dist/tools/corrections.d.ts +4 -0
- package/dist/tools/corrections.d.ts.map +1 -0
- package/dist/tools/corrections.js +69 -0
- package/dist/tools/corrections.js.map +1 -0
- package/dist/tools/data_stores.d.ts +4 -0
- package/dist/tools/data_stores.d.ts.map +1 -0
- package/dist/tools/data_stores.js +244 -0
- package/dist/tools/data_stores.js.map +1 -0
- package/dist/tools/documents.d.ts +4 -0
- package/dist/tools/documents.d.ts.map +1 -0
- package/dist/tools/documents.js +162 -0
- package/dist/tools/documents.js.map +1 -0
- package/dist/tools/extract.d.ts +4 -0
- package/dist/tools/extract.d.ts.map +1 -0
- package/dist/tools/extract.js +61 -0
- package/dist/tools/extract.js.map +1 -0
- package/dist/tools/files.d.ts +4 -0
- package/dist/tools/files.d.ts.map +1 -0
- package/dist/tools/files.js +198 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/golden_answers.d.ts +4 -0
- package/dist/tools/golden_answers.d.ts.map +1 -0
- package/dist/tools/golden_answers.js +117 -0
- package/dist/tools/golden_answers.js.map +1 -0
- package/dist/tools/helpers.d.ts +5 -0
- package/dist/tools/helpers.d.ts.map +1 -0
- package/dist/tools/helpers.js +10 -0
- package/dist/tools/helpers.js.map +1 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +35 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/jobs.d.ts +4 -0
- package/dist/tools/jobs.d.ts.map +1 -0
- package/dist/tools/jobs.js +78 -0
- package/dist/tools/jobs.js.map +1 -0
- package/dist/tools/link_sources.d.ts +4 -0
- package/dist/tools/link_sources.d.ts.map +1 -0
- package/dist/tools/link_sources.js +155 -0
- package/dist/tools/link_sources.js.map +1 -0
- package/dist/tools/misc.d.ts +8 -0
- package/dist/tools/misc.d.ts.map +1 -0
- package/dist/tools/misc.js +73 -0
- package/dist/tools/misc.js.map +1 -0
- package/dist/tools/page_questions.d.ts +4 -0
- package/dist/tools/page_questions.d.ts.map +1 -0
- package/dist/tools/page_questions.js +77 -0
- package/dist/tools/page_questions.js.map +1 -0
- package/dist/tools/personas.d.ts +4 -0
- package/dist/tools/personas.d.ts.map +1 -0
- package/dist/tools/personas.js +78 -0
- package/dist/tools/personas.js.map +1 -0
- package/dist/tools/tools_crud.d.ts +4 -0
- package/dist/tools/tools_crud.d.ts.map +1 -0
- package/dist/tools/tools_crud.js +207 -0
- package/dist/tools/tools_crud.js.map +1 -0
- package/dist/tools/types.d.ts +25 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +15 -0
- package/dist/tools/types.js.map +1 -0
- package/llms.txt +150 -0
- package/package.json +53 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
# @aigentyc/mcp — full LLM context
|
|
2
|
+
|
|
3
|
+
This file is the deep reference for AI coding tools (Cursor, Claude Code, Windsurf, Claude Desktop). It is generated from this package's docs + registered MCP tools at build time. For a short overview, read `llms.txt`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## README.md
|
|
8
|
+
|
|
9
|
+
# @aigentyc/mcp
|
|
10
|
+
|
|
11
|
+
Model Context Protocol server for [aiGentyc](https://aigentyc.com) — lets
|
|
12
|
+
Claude Code, Cursor, Windsurf, and any other MCP-compatible agent drive the
|
|
13
|
+
content/authoring side of your aiGentyc project (documents, crawling, data
|
|
14
|
+
stores, custom tools, config, backups, …) without clicking through the
|
|
15
|
+
dashboard.
|
|
16
|
+
|
|
17
|
+
Chat/search embedding is **not** part of this package — use the separate
|
|
18
|
+
`aigentyc-chat-sdk` (React) for user-facing chat.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @aigentyc/mcp login \
|
|
24
|
+
--api-key tyco_pk_XXXX \
|
|
25
|
+
--project-id proj_XXXX
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`login` verifies the key against `/api/auth/api-keys/verify` and writes
|
|
29
|
+
`~/.aigentyc/config.json` with `0600` perms.
|
|
30
|
+
|
|
31
|
+
**Dev-only flag**: pass `--allow-insecure` to permit plaintext HTTP against
|
|
32
|
+
non-loopback hosts (e.g. a staging box without TLS). Never use this against
|
|
33
|
+
production — all real traffic must be HTTPS.
|
|
34
|
+
|
|
35
|
+
Then wire it into your agent:
|
|
36
|
+
|
|
37
|
+
### Claude Desktop / Claude Code
|
|
38
|
+
|
|
39
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcpServers": {
|
|
44
|
+
"aigentyc": {
|
|
45
|
+
"command": "npx",
|
|
46
|
+
"args": ["-y", "@aigentyc/mcp"]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Cursor
|
|
53
|
+
|
|
54
|
+
Settings → MCP → add server:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"aigentyc": {
|
|
59
|
+
"command": "npx",
|
|
60
|
+
"args": ["-y", "@aigentyc/mcp"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Commands
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
aigentyc-mcp serve Run the stdio MCP server (default when invoked with no args)
|
|
69
|
+
aigentyc-mcp login Save + verify an API key profile
|
|
70
|
+
aigentyc-mcp logout Remove a profile
|
|
71
|
+
aigentyc-mcp doctor Verify config + dashboard reachability
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Tools (v0.1)
|
|
75
|
+
|
|
76
|
+
Vertical slice shipped in this release:
|
|
77
|
+
|
|
78
|
+
- `documents_list`, `documents_get`, `documents_delete`, `documents_create_from_text`
|
|
79
|
+
- `jobs_status`, `jobs_list`, `jobs_wait`
|
|
80
|
+
|
|
81
|
+
More domains (files, link sources, data stores, custom tools, config,
|
|
82
|
+
backups, analytics) land in subsequent releases.
|
|
83
|
+
|
|
84
|
+
## Security
|
|
85
|
+
|
|
86
|
+
- API keys are project-scoped; a key for project A cannot read/write B.
|
|
87
|
+
- `~/.aigentyc/config.json` is written `0600`. The server refuses to start
|
|
88
|
+
with wider perms.
|
|
89
|
+
- The HTTP client refuses plaintext HTTP to non-loopback hosts.
|
|
90
|
+
- Destructive operations (e.g. `documents_delete`) require `confirm: true`.
|
|
91
|
+
- Per-API-key rate limits: 300 reads/min, 60 writes/min (429 over limit).
|
|
92
|
+
- Every API-key-authed request is logged server-side
|
|
93
|
+
(`api_key_audit_log` table) with `keyId`, `projectId`, `route`,
|
|
94
|
+
`method`, `status`, and `X-Request-Id` for tracing.
|
|
95
|
+
- `files_upload` refuses paths that escape `$CWD` or `$HOME`, rejects
|
|
96
|
+
non-regular files, and caps batches at 50MB/file, 500MB total.
|
|
97
|
+
- `extract_from_urls` prefilters RFC1918 / loopback / cloud-metadata URLs.
|
|
98
|
+
|
|
99
|
+
## Publishing
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
cd mcp-server
|
|
103
|
+
npm run build
|
|
104
|
+
npm run smoke # stdio JSON-RPC smoke test
|
|
105
|
+
npm pack --dry-run # inspect what would ship
|
|
106
|
+
npm publish --access public
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Deferred features
|
|
110
|
+
|
|
111
|
+
Tracked for v0.2+:
|
|
112
|
+
|
|
113
|
+
- **`backups_download_all` (ZIP) secret redaction** for API-key callers.
|
|
114
|
+
Current implementation redacts JSON downloads but not the archived ZIP.
|
|
115
|
+
Session callers are unaffected. Recommendation: use session for now.
|
|
116
|
+
- **`/api/extract/*` dual-auth + binary-file uploads** (PDF/DOC/DOCX).
|
|
117
|
+
The extract proxy currently has no auth guard — not exposed to MCP.
|
|
118
|
+
`files_upload` is therefore restricted to UTF-8 text formats only.
|
|
119
|
+
- **Analytics sessions/comments write paths** — MCP is read-only by design.
|
|
120
|
+
- **Custom rate-limit overrides per-key** — one limit for all keys today.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## AGENTS.md
|
|
125
|
+
|
|
126
|
+
# AGENTS.md — @aigentyc/mcp
|
|
127
|
+
|
|
128
|
+
Guide for AI coding assistants (Claude Code, Cursor, Windsurf, Claude Desktop, Codex) using or contributing to this MCP server. Humans: read `README.md` first.
|
|
129
|
+
|
|
130
|
+
**Live docs (always current):** see `context7.json` in this repo for the URL Context7 mirrors.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## What this package is
|
|
135
|
+
|
|
136
|
+
A Model Context Protocol (MCP) server that lets an LLM agent drive the **content & authoring** side of an Aigentyc project: documents, files, link-source crawling, custom tools, data stores, project config, backups, analytics. It is NOT a chat client — for embedding chat into your own app use the sister package `@aigentyc/chat-sdk`.
|
|
137
|
+
|
|
138
|
+
- Package name: `@aigentyc/mcp`
|
|
139
|
+
- Binary: `aigentyc-mcp` (Node 20+, ESM)
|
|
140
|
+
- Transport: stdio (default), wire into Claude Desktop / Cursor MCP config
|
|
141
|
+
- Auth: project-scoped API keys (`tyco_pk_...`) verified by Aigentyc auth-service
|
|
142
|
+
- Storage: `~/.aigentyc/config.json` (0600, refuses to start otherwise)
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Canonical install + usage (paste-ready)
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# 1. Save + verify an API key (one-time per profile)
|
|
150
|
+
npx @aigentyc/mcp login \
|
|
151
|
+
--api-key tyco_pk_XXXX \
|
|
152
|
+
--project-id proj_XXXX
|
|
153
|
+
|
|
154
|
+
# 2. Wire into Claude Desktop / Cursor (one-time)
|
|
155
|
+
# See README for the JSON snippet. Restart the client.
|
|
156
|
+
|
|
157
|
+
# 3. Sanity check
|
|
158
|
+
npx @aigentyc/mcp doctor
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
For local dev against a self-hosted backend on a different port:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
npx @aigentyc/mcp login --api-key ... --project-id ... \
|
|
165
|
+
--base-url http://localhost:3000 \
|
|
166
|
+
--allow-insecure
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Decision tree: which tool to use
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
Need to add markdown / text content to the KB? → files_upload (paths)
|
|
175
|
+
Need to add raw text without a file? → documents_create_from_text
|
|
176
|
+
Need to crawl a public website into the KB? → link_sources_create + jobs_wait
|
|
177
|
+
Need to extract from a list of public URLs? → extract_from_urls + jobs_wait
|
|
178
|
+
Need to add a structured table (rows of data)? → data_stores_create + bulk_upsert
|
|
179
|
+
Need to scrape a website into a data store? → data_stores_scan_website + jobs_wait
|
|
180
|
+
Need to expose a custom action to chat? → tools_create (rest_api / custom_execute / hybrid)
|
|
181
|
+
Need to chain tools? → flows_create
|
|
182
|
+
Need to tweak system prompt or chat model? → config_update
|
|
183
|
+
Need to back up before a risky change? → backups_create + jobs_wait
|
|
184
|
+
Need to inspect what's already in the KB? → search_kb_search (read-only, non-streaming)
|
|
185
|
+
Need to see analytics? → analytics_overview / sessions / comments
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Important behaviors agents should know
|
|
191
|
+
|
|
192
|
+
### 1. Tool inputs are validated by Zod
|
|
193
|
+
|
|
194
|
+
Required string fields enforce `min(1)` server-side via the chat-tools layer. Don't try to call a tool with `""` to "see what happens" — Zod will 400 and the LLM will be told to ask the user for real values.
|
|
195
|
+
|
|
196
|
+
### 2. Async ops return jobIds
|
|
197
|
+
|
|
198
|
+
These tools are intentionally non-blocking:
|
|
199
|
+
`backups_create`, `backups_restore`, `extract_from_urls`, `data_stores_scan_website`, `page_questions_auto_generate`, `files_upload` (when `indexAsDocuments=true` and the batch is large).
|
|
200
|
+
|
|
201
|
+
Pattern:
|
|
202
|
+
```
|
|
203
|
+
const { jobId } = await tool({ ... })
|
|
204
|
+
const result = await jobs_wait({ jobId })
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
There's a per-project concurrency cap of 3 active jobs. If exceeded you get 429.
|
|
208
|
+
|
|
209
|
+
### 3. Destructive ops require `confirm: true`
|
|
210
|
+
|
|
211
|
+
`documents_delete`, `files_delete`, `data_stores_delete`, `tools_delete`, `flows_delete`, `personas_delete`, `link_sources_delete`, `golden_answers_delete`, `backups_restore`, `data_stores_bulk_delete`. The agent should ALWAYS state what's about to be deleted and ask the user to approve before passing `confirm: true`.
|
|
212
|
+
|
|
213
|
+
### 4. Secrets are server-rejected, not just client-warned
|
|
214
|
+
|
|
215
|
+
Calling `config_update({ patch: { openaiApiKey: "..." } })` returns 403 from the server, not a client warning. Same for `customHttpHeaders` and `neonConnectionString`. To rotate secrets, use the dashboard.
|
|
216
|
+
|
|
217
|
+
### 5. Custom tool code can't reference `process.env`
|
|
218
|
+
|
|
219
|
+
`tools_create` / `tools_update` rejects `transformCode` / `executeCode` containing `process.env`. The right way to inject secrets is `apiConfig.headers` with template placeholders that the executor resolves.
|
|
220
|
+
|
|
221
|
+
### 6. URL inputs are SSRF-prefiltered
|
|
222
|
+
|
|
223
|
+
`extract_from_urls`, `link_sources_create` reject RFC1918 / loopback / cloud-metadata hosts both client-side AND server-side. Don't try `192.168.x.x` or `169.254.169.254` — they 400.
|
|
224
|
+
|
|
225
|
+
### 7. File uploads are FS-sandboxed
|
|
226
|
+
|
|
227
|
+
`files_upload` rejects paths outside `$CWD` and `$HOME`, refuses non-regular files, refuses symlinks that escape, caps 50MB/file and 500MB/batch. Text formats only (`.md .txt .json .csv .html .yaml .xml .rtf .log`). Binary formats (PDF/DOC/DOCX) need to go through the dashboard's file upload — out of scope for v0.1.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Common pitfalls
|
|
232
|
+
|
|
233
|
+
- **"Tool created but agent doesn't see it"** → fully quit + relaunch the MCP client. The MCP server registers tools at startup.
|
|
234
|
+
|
|
235
|
+
- **"Created a doc but it's not under /knowledge-base/text"** → check `sourceType`. `documents_create_from_text` always uses `manual`. URLs sourced docs need `extract_from_urls` (which links them to a `link_source`).
|
|
236
|
+
|
|
237
|
+
- **"Tool fires with empty fields"** → the tool's `inputSchema` declared a string param without `required: true`. Update the tool schema.
|
|
238
|
+
|
|
239
|
+
- **"Persona writes return 404"** → upgrade your client; older agents may hit endpoints that didn't have writes.
|
|
240
|
+
|
|
241
|
+
- **"401 from a tool that worked yesterday"** → the API key was probably revoked. Re-run `aigentyc-mcp login`.
|
|
242
|
+
|
|
243
|
+
- **"Tool calls work for me but not from my CI"** → `~/.aigentyc/config.json` doesn't exist in the CI environment. Either run `login` in a build step or set `AIGENTYC_CONFIG=/path/to/config.json` env var pointing at a CI-safe location.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Repository layout
|
|
248
|
+
|
|
249
|
+
```
|
|
250
|
+
src/
|
|
251
|
+
├── cli.ts # entry: serve | login | logout | doctor
|
|
252
|
+
├── server.ts # stdio MCP bootstrap (uses @modelcontextprotocol/sdk)
|
|
253
|
+
├── client.ts # HTTP client (Bearer + X-Request-Id + retries + error map)
|
|
254
|
+
├── config.ts # ~/.aigentyc/config.json read/write (0600 enforced)
|
|
255
|
+
├── errors.ts # AigentycError + httpStatusToCode
|
|
256
|
+
├── fs-safety.ts # path resolve + glob + size cap (used by files_upload)
|
|
257
|
+
└── tools/ # one file per domain; each exports defineTool([])
|
|
258
|
+
├── documents.ts
|
|
259
|
+
├── files.ts
|
|
260
|
+
├── extract.ts
|
|
261
|
+
├── link_sources.ts
|
|
262
|
+
├── page_questions.ts
|
|
263
|
+
├── data_stores.ts
|
|
264
|
+
├── tools_crud.ts # custom tools + flows
|
|
265
|
+
├── golden_answers.ts
|
|
266
|
+
├── corrections.ts
|
|
267
|
+
├── personas.ts
|
|
268
|
+
├── config.ts
|
|
269
|
+
├── backups.ts
|
|
270
|
+
├── analytics.ts
|
|
271
|
+
├── misc.ts # branches, unified_links, audience, kb_search
|
|
272
|
+
├── jobs.ts # status / list / wait
|
|
273
|
+
└── helpers.ts # pid() + projectIdArg
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Adding a new tool
|
|
279
|
+
|
|
280
|
+
1. Pick a domain file (or create a new one if it's a new domain).
|
|
281
|
+
2. Use `defineTool({ name, title, description, inputSchema, handler })` from `./types.js`.
|
|
282
|
+
3. The handler gets parsed args (Zod-validated by the SDK) and an injected `DashboardClient`. Call `client.get/post/patch/delete(path, body|query)`.
|
|
283
|
+
4. Register it in `src/tools/index.ts` if a new domain.
|
|
284
|
+
5. Run `npm run build && npm run smoke` — the smoke test asserts no duplicate names + tool count.
|
|
285
|
+
|
|
286
|
+
Conventions:
|
|
287
|
+
- Tool names: `domain_action` (snake_case). Example: `documents_create_from_text`.
|
|
288
|
+
- Title: short human label.
|
|
289
|
+
- Description: tells the LLM **when** to call the tool, not **how**. Cross-reference adjacent tools when ambiguity is likely (e.g. "for URL-sourced content use extract_from_urls instead").
|
|
290
|
+
- Always use `pid(client, args.projectId)` to default to the active profile's projectId.
|
|
291
|
+
- Destructive tools: include `confirm: z.boolean().default(false)` and short-circuit when `false`.
|
|
292
|
+
- For long-running ops: assume the backend returns `{ jobId, status }` and don't try to wait inline. The user can chain `jobs_wait`.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Releasing a new version
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
node scripts/release.mjs # patch bump (default)
|
|
300
|
+
node scripts/release.mjs minor # minor bump
|
|
301
|
+
node scripts/release.mjs major # major bump
|
|
302
|
+
node scripts/release.mjs 1.2.3 # explicit
|
|
303
|
+
node scripts/release.mjs --dry # rehearsal (no publish)
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The script:
|
|
307
|
+
1. Bumps `package.json` version
|
|
308
|
+
2. Rewrites `context7.json` URL to self-reference the new version on jsdelivr
|
|
309
|
+
3. Runs `npm run build` (TypeScript + llms-full.txt)
|
|
310
|
+
4. `npm publish --access public`
|
|
311
|
+
5. Polls jsdelivr until the published tarball is mirrored
|
|
312
|
+
6. Prints the Context7 URLs for re-registration if needed
|
|
313
|
+
|
|
314
|
+
Requires npm credentials with publish rights on `@aigentyc`.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Where to ask for help
|
|
319
|
+
|
|
320
|
+
- Bugs / feature requests → file in this repo
|
|
321
|
+
- Questions about the **platform** (`app.aigentyc.com`) → main monorepo
|
|
322
|
+
- Questions about embedding a chat UI → `@aigentyc/chat-sdk`
|
|
323
|
+
|
|
324
|
+
---
|
|
325
|
+
|
|
326
|
+
## llms.txt (overview, recipes, troubleshooting)
|
|
327
|
+
|
|
328
|
+
# @aigentyc/mcp
|
|
329
|
+
|
|
330
|
+
> Model Context Protocol server for the Aigentyc platform. Lets Claude Code, Cursor, Windsurf, Claude Desktop, and any MCP-compatible agent drive the **content & authoring** side of an Aigentyc project — documents, files, crawling, custom tools, data stores, config, backups, analytics — without clicking through the dashboard. Chat/widget embedding is handled by a separate package, `@aigentyc/chat-sdk`.
|
|
331
|
+
|
|
332
|
+
## Install
|
|
333
|
+
|
|
334
|
+
```bash
|
|
335
|
+
npx @aigentyc/mcp login \
|
|
336
|
+
--api-key tyco_pk_XXXX \
|
|
337
|
+
--project-id proj_XXXX
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
`login` verifies the key against `/api/auth/api-keys/verify` and writes `~/.aigentyc/config.json` with `0600` perms. The server refuses to start if perms are wider.
|
|
341
|
+
|
|
342
|
+
**Dev-only flag**: pass `--allow-insecure` to permit plaintext HTTP against non-loopback hosts. Never use this against production.
|
|
343
|
+
|
|
344
|
+
## Wire into your agent
|
|
345
|
+
|
|
346
|
+
### Claude Desktop / Claude Code
|
|
347
|
+
|
|
348
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS):
|
|
349
|
+
|
|
350
|
+
```json
|
|
351
|
+
{
|
|
352
|
+
"mcpServers": {
|
|
353
|
+
"aigentyc": {
|
|
354
|
+
"command": "npx",
|
|
355
|
+
"args": ["-y", "@aigentyc/mcp"]
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Cursor
|
|
362
|
+
|
|
363
|
+
Settings → MCP → add server:
|
|
364
|
+
|
|
365
|
+
```json
|
|
366
|
+
{
|
|
367
|
+
"aigentyc": {
|
|
368
|
+
"command": "npx",
|
|
369
|
+
"args": ["-y", "@aigentyc/mcp"]
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Restart the client. The new tools appear under the `aigentyc` namespace.
|
|
375
|
+
|
|
376
|
+
## Commands
|
|
377
|
+
|
|
378
|
+
```
|
|
379
|
+
aigentyc-mcp serve Start the stdio MCP server (default)
|
|
380
|
+
aigentyc-mcp login Save + verify an API key profile
|
|
381
|
+
aigentyc-mcp logout Remove a profile
|
|
382
|
+
aigentyc-mcp doctor Verify config + transport + clock skew + reachability
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
## Tools (82 total, 19 domains)
|
|
386
|
+
|
|
387
|
+
- **documents** — list, get, delete, create_from_text, merge, versions
|
|
388
|
+
- **files** — list, get, upload (path-based), delete, reprocess, versions, bulk_audience
|
|
389
|
+
- **extract** — from_urls (jobified for api-key)
|
|
390
|
+
- **link_sources** — list, get, create, update, delete, list_items, update_item, delete_item
|
|
391
|
+
- **page_questions** — list, set_manual, auto_generate (jobified), delete_page
|
|
392
|
+
- **data_stores** — full CRUD + bulk_upsert + import + export + scan_website (jobified)
|
|
393
|
+
- **tools** (custom-tool CRUD) — list, get, create, update, delete, execute_dry_run
|
|
394
|
+
- **flows** — list, get, create, update, delete
|
|
395
|
+
- **golden_answers** — list, create, update, delete + ai_improve + ai_generate_variations
|
|
396
|
+
- **corrections** — list, create, update_status
|
|
397
|
+
- **personas** — list, upsert, delete
|
|
398
|
+
- **config** — get (secrets redacted for api-key), update (secret writes rejected)
|
|
399
|
+
- **backups** — list, create (jobified), restore (jobified), download, import, delete
|
|
400
|
+
- **analytics** — overview, sessions, session_timeline, comments
|
|
401
|
+
- **branches** — list
|
|
402
|
+
- **search** — kb_search (read-only authoring aid)
|
|
403
|
+
- **jobs** — status, list, wait
|
|
404
|
+
|
|
405
|
+
## Async job pattern
|
|
406
|
+
|
|
407
|
+
Long-running ops (backup create/restore, extract, data-store scan, page-questions auto-generate) return `{ jobId, status: "queued" }`. Poll with `jobs_wait`:
|
|
408
|
+
|
|
409
|
+
```
|
|
410
|
+
extract_from_urls({ urls: [...] })
|
|
411
|
+
→ { jobId: "abc-123", status: "queued" }
|
|
412
|
+
jobs_wait({ jobId: "abc-123" })
|
|
413
|
+
→ blocks until terminal; returns { status: "completed", result: {...} }
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Concurrency cap: 3 active jobs per project.
|
|
417
|
+
|
|
418
|
+
## Security
|
|
419
|
+
|
|
420
|
+
- **Project-scoped keys**: a key for project A cannot read/write project B (403).
|
|
421
|
+
- **Per-key rate limits**: 300 reads/min, 60 writes/min. 429 with `Retry-After` over the limit.
|
|
422
|
+
- **Secrets**: `config.get` redacts `openaiApiKey` + `customHttpHeaders` to `***` for api-key callers. `config.update` rejects writes to those fields with 403. Backup downloads (single + ZIP) deep-redact secrets.
|
|
423
|
+
- **Custom tool code**: `tools.create` rejects `transformCode` / `executeCode` containing `process.env`. Use `apiConfig.headers` with template placeholders for secrets.
|
|
424
|
+
- **Destructive ops** (`*.delete`, `backups.restore`): require `confirm: true`.
|
|
425
|
+
- **Path uploads**: `files.upload` refuses paths outside `$CWD` / `$HOME`, refuses non-regular files, caps 50MB/file, 500MB/batch.
|
|
426
|
+
- **URL inputs**: `extract.from_urls` + `link_sources.create` reject RFC1918 / loopback / cloud-metadata hosts.
|
|
427
|
+
- **Audit log**: every api-key request is logged server-side (`api_key_audit_log` table) with `keyId`, `projectId`, `route`, `method`, `status`, `latencyMs`, `requestId`. Reads sampled at 10%; writes 100%.
|
|
428
|
+
|
|
429
|
+
## Recipes
|
|
430
|
+
|
|
431
|
+
### Add a markdown KB from local files
|
|
432
|
+
|
|
433
|
+
```
|
|
434
|
+
files_upload({ paths: ["docs/intro.md", "docs/api.md"], indexAsDocuments: true })
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
The MCP reads each file (text formats only — `.md`, `.txt`, `.json`, `.csv`, `.html`, …), uploads metadata to `uploaded_files`, then chunks + embeds + indexes via `documents/create`.
|
|
438
|
+
|
|
439
|
+
### Crawl a website + dedupe before adding more docs
|
|
440
|
+
|
|
441
|
+
```
|
|
442
|
+
1. link_sources_create({ sourceUrl: "https://example.com", sourceType: "website" })
|
|
443
|
+
2. jobs_wait({ jobId: ... }) — wait for crawl to finish
|
|
444
|
+
3. search_kb_search({ query: "billing FAQ" }) // confirm it indexed
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Build a custom tool end-to-end
|
|
448
|
+
|
|
449
|
+
```
|
|
450
|
+
1. tools_create({
|
|
451
|
+
name: "submitForm",
|
|
452
|
+
toolType: "rest_api",
|
|
453
|
+
apiConfig: { method: "POST", url: "...", body: "name={{name}}" },
|
|
454
|
+
inputSchema: [{ name: "name", type: "string", required: true }],
|
|
455
|
+
})
|
|
456
|
+
2. tools_execute_dry_run({ toolId, input: { name: "Test" } })
|
|
457
|
+
// verify output before exposing to chat
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Update system prompt
|
|
461
|
+
|
|
462
|
+
```
|
|
463
|
+
config_update({ patch: { systemPrompt: "You are the support agent for ACME..." } })
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
(Cannot update `openaiApiKey` over api-key — use the dashboard for secrets.)
|
|
467
|
+
|
|
468
|
+
## Troubleshooting
|
|
469
|
+
|
|
470
|
+
- **404 on login** — auth-service may be on a different origin in dev. `login` falls back to dashboard ping. Use `--base-url http://localhost:3000` for local Docker.
|
|
471
|
+
- **Tool calls hang** — confirm `aigentyc-mcp doctor` passes. Most failures are wrong base-url, expired key, or clock skew (>60s breaks auth).
|
|
472
|
+
- **Tool created but UI doesn't show it** — fully restart Claude Desktop / Cursor. Server-side caches invalidate on every write; client-side router caches don't.
|
|
473
|
+
- **Empty fields on tool call** — required string fields with `min(1)` get rejected by Zod, prompting the LLM to gather real values from the user. If a tool fires with empty inputs, the field wasn't marked required at creation.
|
|
474
|
+
|
|
475
|
+
## Versioning
|
|
476
|
+
|
|
477
|
+
Server (`@aigentyc/mcp` on npm) and platform (`app.aigentyc.com`) version independently. The platform maintains backward compatibility with older MCP releases — endpoints aren't broken without deprecation.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Tool surface (live snapshot)
|
|
482
|
+
|
|
483
|
+
All MCP tools registered at server startup, grouped by domain prefix. Use these names verbatim when invoking via an MCP client.
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
_Total tools: **82**_
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
### `analytics`
|
|
490
|
+
|
|
491
|
+
| tool | description |
|
|
492
|
+
| ---- | ----------- |
|
|
493
|
+
| `analytics_audience_overview` | Aggregate metrics for audience segments (document counts, coverage). |
|
|
494
|
+
| `analytics_overview` | Aggregate KPIs (sessions, messages, cost, latency, feedback) for a time window. Defaults to the last 30 days. |
|
|
495
|
+
| `analytics_sessions` | Paginated list of chat sessions with summary data, optional text search and date window. |
|
|
496
|
+
| `analytics_session_timeline` | Full Q&A timeline (messages + search events) for a single session. |
|
|
497
|
+
| `analytics_comments` | Project-wide list of admin-authored feedback comments on chat messages, ordered newest first. |
|
|
498
|
+
|
|
499
|
+
### `backups`
|
|
500
|
+
|
|
501
|
+
| tool | description |
|
|
502
|
+
| ---- | ----------- |
|
|
503
|
+
| `backups_list` | List available project backups (Qdrant snapshots + MySQL exports). |
|
|
504
|
+
| `backups_create` | Snapshot the project's Qdrant collections + MySQL data. Synchronous; may take tens of seconds on large projects. |
|
|
505
|
+
| `backups_restore` | Restore the project's Qdrant + MySQL state from a backup. HIGHLY DESTRUCTIVE: overwrites current data. |
|
|
506
|
+
| `backups_download` | Download a backup by index. type='mysql' returns JSON (secrets redacted for API-key callers); type='qdrant' returns an opaque Qdrant snapshot reference. |
|
|
507
|
+
| `backups_import` | Upload a previously-exported backup JSON blob. |
|
|
508
|
+
| `backups_delete` | Delete a backup by index. Destructive. |
|
|
509
|
+
|
|
510
|
+
### `branches`
|
|
511
|
+
|
|
512
|
+
| tool | description |
|
|
513
|
+
| ---- | ----------- |
|
|
514
|
+
| `branches_list` | List named branches of the knowledge base (draft vs published workspaces). |
|
|
515
|
+
|
|
516
|
+
### `config`
|
|
517
|
+
|
|
518
|
+
| tool | description |
|
|
519
|
+
| ---- | ----------- |
|
|
520
|
+
| `config_get` | Fetch the full project configuration. When called via API key, secret fields (openaiApiKey, customHttpHeaders) are redacted to '***'. |
|
|
521
|
+
| `config_update` | Patch project config. Send only the fields you want to change. Secret fields (openaiApiKey, customHttpHeaders, neonConnectionString) cannot be updated via API key and will be rejected with 403. |
|
|
522
|
+
|
|
523
|
+
### `corrections`
|
|
524
|
+
|
|
525
|
+
| tool | description |
|
|
526
|
+
| ---- | ----------- |
|
|
527
|
+
| `corrections_list` | List user feedback / suggested edits against the knowledge base. Corrections are NOT auto-applied — review + mark status. |
|
|
528
|
+
| `corrections_create` | Record a suggested correction for a past Q&A. Status starts as 'pending'. |
|
|
529
|
+
| `corrections_update_status` | Set a correction's status to 'applied' or 'dismissed'. |
|
|
530
|
+
|
|
531
|
+
### `data`
|
|
532
|
+
|
|
533
|
+
| tool | description |
|
|
534
|
+
| ---- | ----------- |
|
|
535
|
+
| `data_stores_list` | List structured data tables (data stores) in the project. |
|
|
536
|
+
| `data_stores_create` | Create a new data store (structured table) with a schema. |
|
|
537
|
+
| `data_stores_get` | Fetch a data store's schema + metadata. |
|
|
538
|
+
| `data_stores_update` | Patch a data store's name, description, columns, or settings. |
|
|
539
|
+
| `data_stores_delete` | Delete a data store and all its rows. Destructive. |
|
|
540
|
+
| `data_stores_list_rows` | List rows in a data store with pagination + optional search. |
|
|
541
|
+
| `data_stores_create_row` | Insert one row into a data store. |
|
|
542
|
+
| `data_stores_update_row` | Update one row in a data store by id. |
|
|
543
|
+
| `data_stores_delete_row` | Delete one row by id. |
|
|
544
|
+
| `data_stores_bulk_upsert` | Insert many rows at once (max 10k). Does not dedupe by default. |
|
|
545
|
+
| `data_stores_bulk_delete` | Delete multiple rows by id in one call. Destructive. |
|
|
546
|
+
| `data_stores_import` | Bulk-import rows (≤10k) with optional column schema. `replaceExisting: true` wipes existing rows first. |
|
|
547
|
+
| `data_stores_export` | Export all rows of a data store as JSON. |
|
|
548
|
+
| `data_stores_scan_website` | Crawl a website and extract structured rows into the store. Returns a jobId; use jobs_wait to block until done. |
|
|
549
|
+
|
|
550
|
+
### `documents`
|
|
551
|
+
|
|
552
|
+
| tool | description |
|
|
553
|
+
| ---- | ----------- |
|
|
554
|
+
| `documents_list` | List documents in a project with pagination, search, and filtering. Returns document summaries (name, chunk count, source). |
|
|
555
|
+
| `documents_get` | Get all chunks of a single document by name, including enrichment metadata. |
|
|
556
|
+
| `documents_delete` | Delete all chunks of a document (including non-current versions) from MySQL and Qdrant. Destructive. |
|
|
557
|
+
| `documents_create_from_text` | Create a new manual-text document in the knowledge base. Chunks, embeds, and indexes synchronously. Shows up under the dashboard's 'Text' tab. For URL-sourced content use extract_from_urls instead (it goes through the crawler and links to a link_source). For file uploads use files_upload. |
|
|
558
|
+
| `documents_merge` | Merge two consecutive chunks of the same document into one. Re-embeds the merged content and updates Qdrant. Provide the merged content verbatim. |
|
|
559
|
+
| `documents_versions` | action=list → return version history for a specific chunk (documentName + chunkIndex). action=create → create a new version of a chunk (documentId + newContent + changeReason). |
|
|
560
|
+
|
|
561
|
+
### `extract`
|
|
562
|
+
|
|
563
|
+
| tool | description |
|
|
564
|
+
| ---- | ----------- |
|
|
565
|
+
| `extract_from_urls` | Batch-extract documents from a list of URLs into the project's knowledge base. Returns synchronously when fast; for large batches the backend may return a jobId (then use jobs_wait). |
|
|
566
|
+
|
|
567
|
+
### `files`
|
|
568
|
+
|
|
569
|
+
| tool | description |
|
|
570
|
+
| ---- | ----------- |
|
|
571
|
+
| `files_list` | List files uploaded into the project's ingestion pipeline. |
|
|
572
|
+
| `files_get` | Get metadata + extraction status for a single uploaded file. |
|
|
573
|
+
| `files_versions` | List all versions of a file (same sourceUrl / family). |
|
|
574
|
+
| `files_delete` | Delete an uploaded file and (optionally) its derived documents. Destructive. |
|
|
575
|
+
| `files_reprocess` | Re-run embedding for all current document chunks derived from this file. Use after changing embedding model or chunk settings. |
|
|
576
|
+
| `files_upload` | Read text-based files (.md, .txt, .json, .csv, .html, …) from the user's local filesystem and ingest them into the project. Binary formats (pdf/doc/docx) are not supported in v0.1. Paths must be absolute or relative to CWD, cannot escape \$HOME, and are capped at 50MB/file and 500MB/batch. |
|
|
577
|
+
| `files_bulk_audience` | Assign an audience tag to multiple files at once. Audiences gate document visibility in search. |
|
|
578
|
+
|
|
579
|
+
### `flows`
|
|
580
|
+
|
|
581
|
+
| tool | description |
|
|
582
|
+
| ---- | ----------- |
|
|
583
|
+
| `flows_list` | List multi-step tool flows (orchestrations) in the project. |
|
|
584
|
+
| `flows_get` | Fetch a single tool flow. |
|
|
585
|
+
| `flows_create` | Create a multi-step tool flow. |
|
|
586
|
+
| `flows_update` | Patch a flow's fields. |
|
|
587
|
+
| `flows_delete` | Delete a flow. Destructive. |
|
|
588
|
+
|
|
589
|
+
### `golden`
|
|
590
|
+
|
|
591
|
+
| tool | description |
|
|
592
|
+
| ---- | ----------- |
|
|
593
|
+
| `golden_answers_list` | List high-priority Q&A pairs used for search ranking and fallback responses. |
|
|
594
|
+
| `golden_answers_create` | Create a golden Q&A pair. |
|
|
595
|
+
| `golden_answers_update` | Patch a golden answer by id. |
|
|
596
|
+
| `golden_answers_delete` | Delete a golden answer by id. Destructive. |
|
|
597
|
+
| `golden_answers_ai_improve` | Ask the AI to rewrite a response for a query in a chosen tone. Returns `improvedResponse` — you decide whether to save. |
|
|
598
|
+
| `golden_answers_ai_generate_variations` | Generate paraphrased variations of a question to improve retrieval. Returns `variations: [{question, context?}]`. |
|
|
599
|
+
|
|
600
|
+
### `jobs`
|
|
601
|
+
|
|
602
|
+
| tool | description |
|
|
603
|
+
| ---- | ----------- |
|
|
604
|
+
| `jobs_status` | Fetch the current status of a long-running job created by another MCP tool. |
|
|
605
|
+
| `jobs_list` | List recent long-running jobs for the project. Useful for debugging. |
|
|
606
|
+
| `jobs_wait` | Poll a job until it reaches a terminal state (completed, failed, cancelled). Returns the final row. |
|
|
607
|
+
|
|
608
|
+
### `kb`
|
|
609
|
+
|
|
610
|
+
| tool | description |
|
|
611
|
+
| ---- | ----------- |
|
|
612
|
+
| `kb_search` | Hybrid / vector search over the project KB. Non-streaming. Use BEFORE creating documents to check for overlap/duplicates. |
|
|
613
|
+
|
|
614
|
+
### `link`
|
|
615
|
+
|
|
616
|
+
| tool | description |
|
|
617
|
+
| ---- | ----------- |
|
|
618
|
+
| `link_sources_list` | List website/pdf link sources configured for the project. Each source tracks discovered URLs + processing state. |
|
|
619
|
+
| `link_sources_get` | Fetch a single link source with its status + discovered item counts. |
|
|
620
|
+
| `link_sources_create` | Register a new website or PDF source and kick off discovery. Returns the source row. |
|
|
621
|
+
| `link_sources_update` | Update audience tags or crawl parameters on an existing source. |
|
|
622
|
+
| `link_sources_delete` | Delete a link source and its discovered items. Does not delete derived documents. Destructive. |
|
|
623
|
+
| `link_sources_list_items` | Paginated list of discovered URLs for a given link source. Filter by isProcessed / isSkipped. |
|
|
624
|
+
| `link_sources_update_item` | Mark a discovered item as processed/skipped. Used to override crawler decisions. |
|
|
625
|
+
| `link_sources_delete_item` | Remove one URL from a link source's discovered set. |
|
|
626
|
+
|
|
627
|
+
### `page`
|
|
628
|
+
|
|
629
|
+
| tool | description |
|
|
630
|
+
| ---- | ----------- |
|
|
631
|
+
| `page_questions_list` | List per-page suggested questions pinned to specific URLs on the user's own site. |
|
|
632
|
+
| `page_questions_set_manual` | Set the suggested questions + welcome text shown for a specific page URL. Overwrites existing. |
|
|
633
|
+
| `page_questions_auto_generate` | Auto-generate Q&A for a page URL (runs AI over scanned document content) and persist as manual questions. Returns a jobId — poll with jobs_wait. |
|
|
634
|
+
| `page_questions_delete_page` | Remove the page questions entry for a specific URL. |
|
|
635
|
+
|
|
636
|
+
### `personas`
|
|
637
|
+
|
|
638
|
+
| tool | description |
|
|
639
|
+
| ---- | ----------- |
|
|
640
|
+
| `personas_list` | List the chat personas configured for the project. Personas are used by the AudienceSelector and define tone/style/custom instructions for different audience segments. |
|
|
641
|
+
| `personas_upsert` | Upsert a chat persona identified by `key`. If a persona with that key exists it is patched, otherwise a new one is created. The persona defines tone, response length, language style, and custom instructions for the chat agent when this persona is active. |
|
|
642
|
+
| `personas_delete` | Delete a persona by key. Destructive. |
|
|
643
|
+
|
|
644
|
+
### `tools`
|
|
645
|
+
|
|
646
|
+
| tool | description |
|
|
647
|
+
| ---- | ----------- |
|
|
648
|
+
| `tools_list` | List every custom tool in the project (rest_api, custom_execute, hybrid, data_store, web_search). |
|
|
649
|
+
| `tools_get` | Fetch one custom tool with its full config. |
|
|
650
|
+
| `tools_create` | Create a custom tool the chat agent can call. Tool code referencing `process.env` is rejected — put secrets in apiConfig.headers with template placeholders. |
|
|
651
|
+
| `tools_update` | Patch selected fields of an existing custom tool. |
|
|
652
|
+
| `tools_delete` | Delete a custom tool. Destructive. |
|
|
653
|
+
| `tools_execute_dry_run` | Invoke a custom tool in test mode with sample input. Safe for iteration; does not count against usage. |
|
|
654
|
+
|
|
655
|
+
### `unified`
|
|
656
|
+
|
|
657
|
+
| tool | description |
|
|
658
|
+
| ---- | ----------- |
|
|
659
|
+
| `unified_links_list` | Get the aggregated catalog of every URL known to the project (files + link sources + manual). |
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAWA,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAyDpD"}
|