@bluenotelogic/mcp 0.2.2
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 +85 -0
- package/dist/api.d.ts +25 -0
- package/dist/api.js +74 -0
- package/dist/api.js.map +1 -0
- package/dist/http.d.ts +25 -0
- package/dist/http.js +158 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +59 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts/draft_with_refs.d.ts +6 -0
- package/dist/prompts/draft_with_refs.js +42 -0
- package/dist/prompts/draft_with_refs.js.map +1 -0
- package/dist/prompts/summarize.d.ts +6 -0
- package/dist/prompts/summarize.js +36 -0
- package/dist/prompts/summarize.js.map +1 -0
- package/dist/resources/corpora.d.ts +8 -0
- package/dist/resources/corpora.js +24 -0
- package/dist/resources/corpora.js.map +1 -0
- package/dist/resources/documents.d.ts +8 -0
- package/dist/resources/documents.js +29 -0
- package/dist/resources/documents.js.map +1 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +48 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/corpus_stats.d.ts +3 -0
- package/dist/tools/corpus_stats.js +21 -0
- package/dist/tools/corpus_stats.js.map +1 -0
- package/dist/tools/get_document.d.ts +3 -0
- package/dist/tools/get_document.js +35 -0
- package/dist/tools/get_document.js.map +1 -0
- package/dist/tools/ingest_text.d.ts +3 -0
- package/dist/tools/ingest_text.js +43 -0
- package/dist/tools/ingest_text.js.map +1 -0
- package/dist/tools/list_corpora.d.ts +3 -0
- package/dist/tools/list_corpora.js +28 -0
- package/dist/tools/list_corpora.js.map +1 -0
- package/dist/tools/list_profiles.d.ts +3 -0
- package/dist/tools/list_profiles.js +30 -0
- package/dist/tools/list_profiles.js.map +1 -0
- package/dist/tools/search.d.ts +3 -0
- package/dist/tools/search.js +70 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# @bluenotelogic/mcp
|
|
2
|
+
|
|
3
|
+
MCP server for CaveauAI — inject corpus content into Claude Code, Claude Desktop, Cursor, Continue, or any other MCP host at runtime, scoped to your retrieval profiles. Published under Blue Note Logic's npm scope; serves the CaveauAI product.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @bluenotelogic/mcp@latest --stdio
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Configure
|
|
12
|
+
|
|
13
|
+
Set two environment variables:
|
|
14
|
+
|
|
15
|
+
| Variable | Required | Default | Notes |
|
|
16
|
+
|----------|----------|---------|-------|
|
|
17
|
+
| `CAVEAU_API_TOKEN` | yes (stdio) | — | Mint one in the portal at `caveauai.bluenotelogic.com/app/mcp`. Shown once. |
|
|
18
|
+
| `CAVEAU_BASE_URL` | no | `https://ai.bluenotelogic.com` | Set this if you self-host or talk to staging. |
|
|
19
|
+
|
|
20
|
+
## Claude Desktop
|
|
21
|
+
|
|
22
|
+
Add to `claude_desktop_config.json`:
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"caveau": {
|
|
28
|
+
"command": "npx",
|
|
29
|
+
"args": ["-y", "@bluenotelogic/mcp", "--stdio"],
|
|
30
|
+
"env": {
|
|
31
|
+
"CAVEAU_API_TOKEN": "mcp_…"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Claude Code
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
claude mcp add caveau -- npx -y @bluenotelogic/mcp --stdio
|
|
42
|
+
# Then set CAVEAU_API_TOKEN in your shell or via `claude mcp env`
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Hosted Streamable HTTP
|
|
46
|
+
|
|
47
|
+
For hosts that support remote MCP (Claude Desktop, Cursor with the latest connectors), use the hosted URL instead of npx:
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
https://mcp.caveauai.bluenotelogic.com/mcp
|
|
51
|
+
Authorization: Bearer mcp_…
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Tools
|
|
55
|
+
|
|
56
|
+
| Tool | Purpose |
|
|
57
|
+
|------|---------|
|
|
58
|
+
| `caveau.search` | RAG search; returns ranked chunks with source metadata. Optional `profile`, `top_k`, `category`. |
|
|
59
|
+
| `caveau.list_profiles` | List your saved retrieval profiles (named criteria sets). |
|
|
60
|
+
| `caveau.list_corpora` | List corpora you can query — your private ones plus shared subscriptions. |
|
|
61
|
+
| `caveau.get_document` | Fetch a single document and its chunks by id. |
|
|
62
|
+
| `caveau.corpus_stats` | Per-corpus document/chunk counts plus your plan caps. |
|
|
63
|
+
| `caveau.ingest_text` | Add text to one of your private corpora (plan-gated). |
|
|
64
|
+
|
|
65
|
+
## Retrieval profiles
|
|
66
|
+
|
|
67
|
+
A profile is a saved bundle of *what to retrieve*: which corpora, which search method (vector/keyword/hybrid), top_k, recency cutoff, category and tag whitelists, embedding-model override. Build them in the portal at `caveauai.bluenotelogic.com/app/mcp` and reference them by slug from `caveau.search`.
|
|
68
|
+
|
|
69
|
+
## Local development
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install
|
|
73
|
+
npm run build
|
|
74
|
+
CAVEAU_API_TOKEN=… node dist/index.js --stdio
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Test the server end-to-end with the official inspector:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx @modelcontextprotocol/inspector node dist/index.js --stdio
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## License
|
|
84
|
+
|
|
85
|
+
UNLICENSED — © Blue Note Logic.
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin HTTP client for the CaveauAI MCP runtime API at /api/mcp/*.
|
|
3
|
+
* All calls forward the client's MCP token via the X-MCP-Token header.
|
|
4
|
+
* The server enforces tenant isolation and quota; this client just bubbles
|
|
5
|
+
* up the JSON body and HTTP status.
|
|
6
|
+
*/
|
|
7
|
+
export interface ApiConfig {
|
|
8
|
+
baseUrl: string;
|
|
9
|
+
token: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare class ApiError extends Error {
|
|
13
|
+
readonly status: number;
|
|
14
|
+
readonly body: unknown;
|
|
15
|
+
constructor(status: number, body: unknown, message: string);
|
|
16
|
+
}
|
|
17
|
+
export declare class CaveauApi {
|
|
18
|
+
private readonly base;
|
|
19
|
+
private readonly token;
|
|
20
|
+
private readonly timeoutMs;
|
|
21
|
+
constructor(cfg: ApiConfig);
|
|
22
|
+
get<T>(path: string): Promise<T>;
|
|
23
|
+
post<T>(path: string, body: unknown): Promise<T>;
|
|
24
|
+
private request;
|
|
25
|
+
}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin HTTP client for the CaveauAI MCP runtime API at /api/mcp/*.
|
|
3
|
+
* All calls forward the client's MCP token via the X-MCP-Token header.
|
|
4
|
+
* The server enforces tenant isolation and quota; this client just bubbles
|
|
5
|
+
* up the JSON body and HTTP status.
|
|
6
|
+
*/
|
|
7
|
+
export class ApiError extends Error {
|
|
8
|
+
status;
|
|
9
|
+
body;
|
|
10
|
+
constructor(status, body, message) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.status = status;
|
|
13
|
+
this.body = body;
|
|
14
|
+
this.name = "ApiError";
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class CaveauApi {
|
|
18
|
+
base;
|
|
19
|
+
token;
|
|
20
|
+
timeoutMs;
|
|
21
|
+
constructor(cfg) {
|
|
22
|
+
this.base = cfg.baseUrl.replace(/\/+$/, "");
|
|
23
|
+
this.token = cfg.token;
|
|
24
|
+
this.timeoutMs = cfg.timeoutMs ?? 30_000;
|
|
25
|
+
if (!this.token)
|
|
26
|
+
throw new Error("CAVEAU_API_TOKEN is required");
|
|
27
|
+
if (!this.base)
|
|
28
|
+
throw new Error("CAVEAU_BASE_URL is required");
|
|
29
|
+
}
|
|
30
|
+
async get(path) {
|
|
31
|
+
return this.request("GET", path);
|
|
32
|
+
}
|
|
33
|
+
async post(path, body) {
|
|
34
|
+
return this.request("POST", path, body);
|
|
35
|
+
}
|
|
36
|
+
async request(method, path, body) {
|
|
37
|
+
const url = `${this.base}/api/mcp${path.startsWith("/") ? path : `/${path}`}`;
|
|
38
|
+
const ctrl = new AbortController();
|
|
39
|
+
const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(url, {
|
|
42
|
+
method,
|
|
43
|
+
headers: {
|
|
44
|
+
"X-MCP-Token": this.token,
|
|
45
|
+
"Content-Type": "application/json",
|
|
46
|
+
Accept: "application/json",
|
|
47
|
+
},
|
|
48
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
49
|
+
signal: ctrl.signal,
|
|
50
|
+
});
|
|
51
|
+
const text = await res.text();
|
|
52
|
+
let parsed = null;
|
|
53
|
+
if (text.length > 0) {
|
|
54
|
+
try {
|
|
55
|
+
parsed = JSON.parse(text);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
parsed = text;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!res.ok) {
|
|
62
|
+
const detail = parsed && typeof parsed === "object" && "error" in parsed
|
|
63
|
+
? String(parsed.error)
|
|
64
|
+
: `HTTP ${res.status}`;
|
|
65
|
+
throw new ApiError(res.status, parsed, detail);
|
|
66
|
+
}
|
|
67
|
+
return parsed;
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
clearTimeout(timer);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP transport for caveau-mcp.
|
|
3
|
+
*
|
|
4
|
+
* Multi-tenant: each MCP session is tied to a per-client token presented as
|
|
5
|
+
* `Authorization: Bearer <token>` on the initialize request. The token is
|
|
6
|
+
* validated upfront by hitting /api/mcp/profiles (cheap, tenant-scoped). All
|
|
7
|
+
* subsequent tool calls in the session reuse that token.
|
|
8
|
+
*
|
|
9
|
+
* Three routes per the MCP spec:
|
|
10
|
+
* POST /mcp JSON-RPC requests (initialize + subsequent calls)
|
|
11
|
+
* GET /mcp SSE stream from server to client
|
|
12
|
+
* DELETE /mcp Tear down a session
|
|
13
|
+
*
|
|
14
|
+
* Plus an unauthenticated /healthz for Caddy + Uptime Kuma.
|
|
15
|
+
*/
|
|
16
|
+
export interface HttpServerOptions {
|
|
17
|
+
port: number;
|
|
18
|
+
apiBaseUrl: string;
|
|
19
|
+
/**
|
|
20
|
+
* If true, allow requests without a Bearer token (token is then taken from
|
|
21
|
+
* CAVEAU_API_TOKEN env). Useful for single-tenant test deployments. Default false.
|
|
22
|
+
*/
|
|
23
|
+
allowEnvFallback?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare function startHttpServer(opts: HttpServerOptions): Promise<void>;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP transport for caveau-mcp.
|
|
3
|
+
*
|
|
4
|
+
* Multi-tenant: each MCP session is tied to a per-client token presented as
|
|
5
|
+
* `Authorization: Bearer <token>` on the initialize request. The token is
|
|
6
|
+
* validated upfront by hitting /api/mcp/profiles (cheap, tenant-scoped). All
|
|
7
|
+
* subsequent tool calls in the session reuse that token.
|
|
8
|
+
*
|
|
9
|
+
* Three routes per the MCP spec:
|
|
10
|
+
* POST /mcp JSON-RPC requests (initialize + subsequent calls)
|
|
11
|
+
* GET /mcp SSE stream from server to client
|
|
12
|
+
* DELETE /mcp Tear down a session
|
|
13
|
+
*
|
|
14
|
+
* Plus an unauthenticated /healthz for Caddy + Uptime Kuma.
|
|
15
|
+
*/
|
|
16
|
+
import express from "express";
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
18
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
19
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
20
|
+
import { buildServer } from "./server.js";
|
|
21
|
+
export async function startHttpServer(opts) {
|
|
22
|
+
const sessions = new Map();
|
|
23
|
+
const app = express();
|
|
24
|
+
// Health route (no auth, no body parsing) — wired before json middleware so it never blocks
|
|
25
|
+
app.get("/healthz", (_req, res) => {
|
|
26
|
+
res.status(200).json({
|
|
27
|
+
status: "ok",
|
|
28
|
+
service: "caveau-mcp",
|
|
29
|
+
sessions: sessions.size,
|
|
30
|
+
time: new Date().toISOString(),
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
app.use(express.json({ limit: "1mb" }));
|
|
34
|
+
app.post("/mcp", async (req, res) => {
|
|
35
|
+
try {
|
|
36
|
+
const sessionId = headerString(req, "mcp-session-id");
|
|
37
|
+
let entry = sessionId ? sessions.get(sessionId) : undefined;
|
|
38
|
+
if (!entry && sessionId) {
|
|
39
|
+
// Session id supplied but unknown — likely stale
|
|
40
|
+
respondError(res, -32000, "Unknown or expired session", 404);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (!entry) {
|
|
44
|
+
// Brand-new session — must be an initialize request
|
|
45
|
+
if (!isInitializeRequest(req.body)) {
|
|
46
|
+
respondError(res, -32000, "First request must be initialize", 400);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const token = extractBearer(req) ?? (opts.allowEnvFallback ? (process.env.CAVEAU_API_TOKEN ?? "") : "");
|
|
50
|
+
if (!token) {
|
|
51
|
+
respondError(res, -32001, "Authorization: Bearer <mcp-token> required", 401);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const ok = await validateToken(opts.apiBaseUrl, token);
|
|
55
|
+
if (!ok) {
|
|
56
|
+
respondError(res, -32001, "Invalid or revoked MCP token", 401);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const transport = new StreamableHTTPServerTransport({
|
|
60
|
+
sessionIdGenerator: () => randomUUID(),
|
|
61
|
+
onsessioninitialized: (id) => {
|
|
62
|
+
sessions.set(id, { transport, token, createdAt: Date.now() });
|
|
63
|
+
console.log(`[caveau-mcp] session ${id} opened (${sessions.size} active)`);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
transport.onclose = () => {
|
|
67
|
+
if (transport.sessionId) {
|
|
68
|
+
sessions.delete(transport.sessionId);
|
|
69
|
+
console.log(`[caveau-mcp] session ${transport.sessionId} closed (${sessions.size} active)`);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
const server = buildServer({
|
|
73
|
+
apiBaseUrl: opts.apiBaseUrl,
|
|
74
|
+
apiToken: token,
|
|
75
|
+
});
|
|
76
|
+
await server.connect(transport);
|
|
77
|
+
await transport.handleRequest(req, res, req.body);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
await entry.transport.handleRequest(req, res, req.body);
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
console.error("[caveau-mcp] POST /mcp error:", err);
|
|
84
|
+
if (!res.headersSent) {
|
|
85
|
+
respondError(res, -32603, "Internal error", 500);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
app.get("/mcp", async (req, res) => {
|
|
90
|
+
const sessionId = headerString(req, "mcp-session-id");
|
|
91
|
+
const entry = sessionId ? sessions.get(sessionId) : undefined;
|
|
92
|
+
if (!entry) {
|
|
93
|
+
res.status(404).json({ error: "Unknown or expired session" });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
await entry.transport.handleRequest(req, res);
|
|
97
|
+
});
|
|
98
|
+
app.delete("/mcp", async (req, res) => {
|
|
99
|
+
const sessionId = headerString(req, "mcp-session-id");
|
|
100
|
+
const entry = sessionId ? sessions.get(sessionId) : undefined;
|
|
101
|
+
if (!entry) {
|
|
102
|
+
res.status(404).json({ error: "Unknown or expired session" });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await entry.transport.handleRequest(req, res);
|
|
106
|
+
});
|
|
107
|
+
await new Promise((resolve) => {
|
|
108
|
+
app.listen(opts.port, () => {
|
|
109
|
+
console.log(`[caveau-mcp] Streamable HTTP listening on :${opts.port}`);
|
|
110
|
+
resolve();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function headerString(req, name) {
|
|
115
|
+
const v = req.headers[name];
|
|
116
|
+
if (typeof v === "string")
|
|
117
|
+
return v;
|
|
118
|
+
if (Array.isArray(v))
|
|
119
|
+
return v[0];
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
function extractBearer(req) {
|
|
123
|
+
const auth = headerString(req, "authorization");
|
|
124
|
+
if (!auth)
|
|
125
|
+
return undefined;
|
|
126
|
+
const m = /^Bearer\s+(.+)$/i.exec(auth);
|
|
127
|
+
return m?.[1]?.trim();
|
|
128
|
+
}
|
|
129
|
+
async function validateToken(apiBaseUrl, token) {
|
|
130
|
+
try {
|
|
131
|
+
const url = `${apiBaseUrl.replace(/\/+$/, "")}/api/mcp/profiles`;
|
|
132
|
+
const ctrl = new AbortController();
|
|
133
|
+
const timer = setTimeout(() => ctrl.abort(), 8_000);
|
|
134
|
+
try {
|
|
135
|
+
const res = await fetch(url, {
|
|
136
|
+
method: "GET",
|
|
137
|
+
headers: { "X-MCP-Token": token, Accept: "application/json" },
|
|
138
|
+
signal: ctrl.signal,
|
|
139
|
+
});
|
|
140
|
+
return res.ok;
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
clearTimeout(timer);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
console.error("[caveau-mcp] token validation failed:", err);
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function respondError(res, code, message, http) {
|
|
152
|
+
res.status(http).json({
|
|
153
|
+
jsonrpc: "2.0",
|
|
154
|
+
error: { code, message },
|
|
155
|
+
id: null,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* caveau-mcp — CaveauAI's MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Two transports from a single binary:
|
|
6
|
+
* --stdio Pipe Claude Code/Desktop stdio to the CaveauAI runtime API.
|
|
7
|
+
* This is what `npx @caveauai/mcp --stdio` runs.
|
|
8
|
+
* --http Streamable HTTP transport on PORT (default 3000). This is the
|
|
9
|
+
* hosted-mode binary that runs in Docker on Colin behind Caddy at
|
|
10
|
+
* mcp.caveauai.bluenotelogic.com.
|
|
11
|
+
*
|
|
12
|
+
* Required env:
|
|
13
|
+
* CAVEAU_API_TOKEN the per-client MCP token minted in the portal
|
|
14
|
+
* CAVEAU_BASE_URL base URL for ai-portal (default https://ai.bluenotelogic.com)
|
|
15
|
+
*
|
|
16
|
+
* In --http mode, CAVEAU_API_TOKEN is unused at startup; each request supplies
|
|
17
|
+
* its own token via the standard MCP Authorization header (forwarded as
|
|
18
|
+
* X-MCP-Token).
|
|
19
|
+
*/
|
|
20
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* caveau-mcp — CaveauAI's MCP server.
|
|
4
|
+
*
|
|
5
|
+
* Two transports from a single binary:
|
|
6
|
+
* --stdio Pipe Claude Code/Desktop stdio to the CaveauAI runtime API.
|
|
7
|
+
* This is what `npx @caveauai/mcp --stdio` runs.
|
|
8
|
+
* --http Streamable HTTP transport on PORT (default 3000). This is the
|
|
9
|
+
* hosted-mode binary that runs in Docker on Colin behind Caddy at
|
|
10
|
+
* mcp.caveauai.bluenotelogic.com.
|
|
11
|
+
*
|
|
12
|
+
* Required env:
|
|
13
|
+
* CAVEAU_API_TOKEN the per-client MCP token minted in the portal
|
|
14
|
+
* CAVEAU_BASE_URL base URL for ai-portal (default https://ai.bluenotelogic.com)
|
|
15
|
+
*
|
|
16
|
+
* In --http mode, CAVEAU_API_TOKEN is unused at startup; each request supplies
|
|
17
|
+
* its own token via the standard MCP Authorization header (forwarded as
|
|
18
|
+
* X-MCP-Token).
|
|
19
|
+
*/
|
|
20
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
21
|
+
import { buildServer } from "./server.js";
|
|
22
|
+
import { startHttpServer } from "./http.js";
|
|
23
|
+
const args = new Set(process.argv.slice(2));
|
|
24
|
+
const useStdio = args.has("--stdio");
|
|
25
|
+
const useHttp = args.has("--http");
|
|
26
|
+
if (useStdio === useHttp) {
|
|
27
|
+
// Default to stdio when neither is given (the npx case).
|
|
28
|
+
if (!useStdio && !useHttp) {
|
|
29
|
+
args.add("--stdio");
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
fail("Pass exactly one of --stdio or --http");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const baseUrl = (process.env.CAVEAU_BASE_URL ?? "https://ai.bluenotelogic.com").replace(/\/+$/, "");
|
|
36
|
+
if (args.has("--stdio")) {
|
|
37
|
+
const token = process.env.CAVEAU_API_TOKEN ?? "";
|
|
38
|
+
if (!token)
|
|
39
|
+
fail("CAVEAU_API_TOKEN env var is required for --stdio mode");
|
|
40
|
+
const server = buildServer({ apiBaseUrl: baseUrl, apiToken: token });
|
|
41
|
+
const transport = new StdioServerTransport();
|
|
42
|
+
await server.connect(transport);
|
|
43
|
+
// Block; transport stays open until the host closes stdin.
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const port = parseInt(process.env.PORT ?? "3000", 10);
|
|
47
|
+
if (!Number.isFinite(port) || port < 1 || port > 65535)
|
|
48
|
+
fail(`Bad PORT: ${process.env.PORT}`);
|
|
49
|
+
await startHttpServer({
|
|
50
|
+
port,
|
|
51
|
+
apiBaseUrl: baseUrl,
|
|
52
|
+
allowEnvFallback: process.env.CAVEAU_ALLOW_ENV_FALLBACK === "1",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function fail(msg) {
|
|
56
|
+
process.stderr.write(`caveau-mcp: ${msg}\n`);
|
|
57
|
+
process.exit(2);
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE5C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AACrC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAEnC,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;IACzB,yDAAyD;IACzD,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO,EAAE,CAAC;QAC1B,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IACtB,CAAC;SAAM,CAAC;QACN,IAAI,CAAC,uCAAuC,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,8BAA8B,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEpG,IAAI,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC;IACjD,IAAI,CAAC,KAAK;QAAE,IAAI,CAAC,uDAAuD,CAAC,CAAC;IAC1E,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,2DAA2D;AAC7D,CAAC;KAAM,CAAC;IACN,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,MAAM,EAAE,EAAE,CAAC,CAAC;IACtD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,KAAK;QAAE,IAAI,CAAC,aAAa,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9F,MAAM,eAAe,CAAC;QACpB,IAAI;QACJ,UAAU,EAAE,OAAO;QACnB,gBAAgB,EAAE,OAAO,CAAC,GAAG,CAAC,yBAAyB,KAAK,GAAG;KAChE,CAAC,CAAC;AACL,CAAC;AAED,SAAS,IAAI,CAAC,GAAW;IACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* "Draft <doc> citing my corpus" — primes the model to write a longer artefact
|
|
4
|
+
* (memo, brief, email) grounded in CaveauAI corpus chunks with footnotes.
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerDraftWithRefsPrompt(server: McpServer): void;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* "Draft <doc> citing my corpus" — primes the model to write a longer artefact
|
|
4
|
+
* (memo, brief, email) grounded in CaveauAI corpus chunks with footnotes.
|
|
5
|
+
*/
|
|
6
|
+
export function registerDraftWithRefsPrompt(server) {
|
|
7
|
+
server.registerPrompt("caveau.draft_with_refs", {
|
|
8
|
+
title: "Draft a document citing my corpus",
|
|
9
|
+
description: "Draft a longer artefact (memo, briefing, email) backed by retrieved chunks from the client's CaveauAI corpus, with footnote citations.",
|
|
10
|
+
argsSchema: {
|
|
11
|
+
artefact: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe("What to draft (e.g. 'a one-page board memo on the new merger filing rules')."),
|
|
14
|
+
audience: z
|
|
15
|
+
.string()
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Who it's for (e.g. 'CEO', 'external counsel'). Shapes tone and depth."),
|
|
18
|
+
profile: z.string().optional().describe("Optional retrieval profile slug."),
|
|
19
|
+
},
|
|
20
|
+
}, ({ artefact, audience, profile }) => {
|
|
21
|
+
const profileLine = profile ? ` (profile \`${profile}\`)` : "";
|
|
22
|
+
const audienceLine = audience ? ` Audience: ${audience}.` : "";
|
|
23
|
+
return {
|
|
24
|
+
messages: [
|
|
25
|
+
{
|
|
26
|
+
role: "user",
|
|
27
|
+
content: {
|
|
28
|
+
type: "text",
|
|
29
|
+
text: `Draft ${artefact}.${audienceLine}\n\n` +
|
|
30
|
+
`Process:\n` +
|
|
31
|
+
`1. Use \`caveau.list_corpora\` to confirm what's available if you're unsure.\n` +
|
|
32
|
+
`2. Run \`caveau.search\`${profileLine} — at least once, ideally twice with different angles.\n` +
|
|
33
|
+
`3. Write the artefact in the appropriate format. Every load-bearing factual claim needs a numbered footnote.\n` +
|
|
34
|
+
`4. After the artefact, list the footnotes — each with the source title and source_url.\n` +
|
|
35
|
+
`5. If a claim isn't supported by retrieved chunks, either remove it or flag it as "[unsourced]".`,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=draft_with_refs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft_with_refs.js","sourceRoot":"","sources":["../../src/prompts/draft_with_refs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,MAAiB;IAC3D,MAAM,CAAC,cAAc,CACnB,wBAAwB,EACxB;QACE,KAAK,EAAE,mCAAmC;QAC1C,WAAW,EACT,wIAAwI;QAC1I,UAAU,EAAE;YACV,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,CAAC,8EAA8E,CAAC;YAC3F,QAAQ,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,uEAAuE,CAAC;YACpF,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,kCAAkC,CAAC;SAC5E;KACF,EACD,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;QAClC,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,eAAe,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,cAAc,QAAQ,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE;wBACP,IAAI,EAAE,MAAM;wBACZ,IAAI,EACF,SAAS,QAAQ,IAAI,YAAY,MAAM;4BACvC,YAAY;4BACZ,gFAAgF;4BAChF,2BAA2B,WAAW,0DAA0D;4BAChG,gHAAgH;4BAChH,0FAA0F;4BAC1F,kGAAkG;qBACrG;iBACF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* "Summarize <topic> using my CaveauAI corpus" — primes the host model to use
|
|
4
|
+
* caveau.search before answering.
|
|
5
|
+
*/
|
|
6
|
+
export declare function registerSummarizePrompt(server: McpServer): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* "Summarize <topic> using my CaveauAI corpus" — primes the host model to use
|
|
4
|
+
* caveau.search before answering.
|
|
5
|
+
*/
|
|
6
|
+
export function registerSummarizePrompt(server) {
|
|
7
|
+
server.registerPrompt("caveau.summarize_with_corpus", {
|
|
8
|
+
title: "Summarise from my corpus",
|
|
9
|
+
description: "Ask the model to summarise a topic by first searching the client's CaveauAI corpus and citing the chunks it used.",
|
|
10
|
+
argsSchema: {
|
|
11
|
+
topic: z.string().describe("What to summarise (e.g. 'EU AI Act enforcement timeline')."),
|
|
12
|
+
profile: z.string().optional().describe("Optional retrieval profile slug to bias the search."),
|
|
13
|
+
max_words: z.string().optional().describe("Optional length cap as a number (e.g. '300')."),
|
|
14
|
+
},
|
|
15
|
+
}, ({ topic, profile, max_words }) => {
|
|
16
|
+
const profileLine = profile ? ` (use profile \`${profile}\`)` : "";
|
|
17
|
+
const lengthLine = max_words ? ` Keep it under ${max_words} words.` : "";
|
|
18
|
+
return {
|
|
19
|
+
messages: [
|
|
20
|
+
{
|
|
21
|
+
role: "user",
|
|
22
|
+
content: {
|
|
23
|
+
type: "text",
|
|
24
|
+
text: `Summarise the following from my CaveauAI corpus: ${topic}.\n\n` +
|
|
25
|
+
`1. First call \`caveau.search\` with a query that captures the topic${profileLine}.\n` +
|
|
26
|
+
`2. Read the returned chunks. If they're sparse, run a second search with a refined query.\n` +
|
|
27
|
+
`3. Write a tight summary using ONLY the retrieved chunks as evidence.\n` +
|
|
28
|
+
`4. End with a "Sources" section listing each chunk you actually used (title + source_url).\n` +
|
|
29
|
+
`${lengthLine}`,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=summarize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summarize.js","sourceRoot":"","sources":["../../src/prompts/summarize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,CAAC,cAAc,CACnB,8BAA8B,EAC9B;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,mHAAmH;QACrH,UAAU,EAAE;YACV,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4DAA4D,CAAC;YACxF,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,qDAAqD,CAAC;YAC9F,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;SAC3F;KACF,EACD,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE;QAChC,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,mBAAmB,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QACnE,MAAM,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,kBAAkB,SAAS,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACzE,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE;wBACP,IAAI,EAAE,MAAM;wBACZ,IAAI,EACF,oDAAoD,KAAK,OAAO;4BAChE,uEAAuE,WAAW,KAAK;4BACvF,6FAA6F;4BAC7F,yEAAyE;4BACzE,8FAA8F;4BAC9F,GAAG,UAAU,EAAE;qBAClB;iBACF;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CaveauApi } from "../api.js";
|
|
3
|
+
/**
|
|
4
|
+
* Exposes the client's accessible corpora as a single MCP resource. Hosts that
|
|
5
|
+
* support resource browsing (Claude Desktop "Attach context") can pull the
|
|
6
|
+
* full list and let the user pick from it before posing a question.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerCorporaResource(server: McpServer, api: CaveauApi): void;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exposes the client's accessible corpora as a single MCP resource. Hosts that
|
|
3
|
+
* support resource browsing (Claude Desktop "Attach context") can pull the
|
|
4
|
+
* full list and let the user pick from it before posing a question.
|
|
5
|
+
*/
|
|
6
|
+
export function registerCorporaResource(server, api) {
|
|
7
|
+
server.registerResource("caveau-corpora", "caveau://corpora", {
|
|
8
|
+
title: "Corpora",
|
|
9
|
+
description: "All corpora this CaveauAI client can query.",
|
|
10
|
+
mimeType: "application/json",
|
|
11
|
+
}, async (uri) => {
|
|
12
|
+
const res = await api.get("/corpora");
|
|
13
|
+
return {
|
|
14
|
+
contents: [
|
|
15
|
+
{
|
|
16
|
+
uri: uri.href,
|
|
17
|
+
mimeType: "application/json",
|
|
18
|
+
text: JSON.stringify(res, null, 2),
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=corpora.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpora.js","sourceRoot":"","sources":["../../src/resources/corpora.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAiB,EAAE,GAAc;IACvE,MAAM,CAAC,gBAAgB,CACrB,gBAAgB,EAChB,kBAAkB,EAClB;QACE,KAAK,EAAE,SAAS;QAChB,WAAW,EAAE,6CAA6C;QAC1D,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,EAAE,GAAG,EAAE,EAAE;QACZ,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAkB,UAAU,CAAC,CAAC;QACvD,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,GAAG,CAAC,IAAI;oBACb,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;iBACnC;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { CaveauApi } from "../api.js";
|
|
3
|
+
/**
|
|
4
|
+
* URI-template resource: caveau://documents/{id} resolves to the full text +
|
|
5
|
+
* chunk metadata of one of the client's documents. Useful when search returns
|
|
6
|
+
* a chunk and the host wants to pull the full document into context.
|
|
7
|
+
*/
|
|
8
|
+
export declare function registerDocumentsResource(server: McpServer, api: CaveauApi): void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
/**
|
|
3
|
+
* URI-template resource: caveau://documents/{id} resolves to the full text +
|
|
4
|
+
* chunk metadata of one of the client's documents. Useful when search returns
|
|
5
|
+
* a chunk and the host wants to pull the full document into context.
|
|
6
|
+
*/
|
|
7
|
+
export function registerDocumentsResource(server, api) {
|
|
8
|
+
server.registerResource("caveau-document", new ResourceTemplate("caveau://documents/{id}", { list: undefined }), {
|
|
9
|
+
title: "Document",
|
|
10
|
+
description: "Fetch a single CaveauAI document by id (with all chunks).",
|
|
11
|
+
mimeType: "application/json",
|
|
12
|
+
}, async (uri, vars) => {
|
|
13
|
+
const id = String(vars.id ?? "");
|
|
14
|
+
if (!/^\d+$/.test(id)) {
|
|
15
|
+
throw new Error(`document id must be numeric, got "${id}"`);
|
|
16
|
+
}
|
|
17
|
+
const res = await api.get(`/documents/${id}`);
|
|
18
|
+
return {
|
|
19
|
+
contents: [
|
|
20
|
+
{
|
|
21
|
+
uri: uri.href,
|
|
22
|
+
mimeType: "application/json",
|
|
23
|
+
text: JSON.stringify(res, null, 2),
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=documents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"documents.js","sourceRoot":"","sources":["../../src/resources/documents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yCAAyC,CAAC;AAK3E;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,MAAiB,EAAE,GAAc;IACzE,MAAM,CAAC,gBAAgB,CACrB,iBAAiB,EACjB,IAAI,gBAAgB,CAAC,yBAAyB,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EACpE;QACE,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,2DAA2D;QACxE,QAAQ,EAAE,kBAAkB;KAC7B,EACD,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAClB,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QACjC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,qCAAqC,EAAE,GAAG,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAmB,cAAc,EAAE,EAAE,CAAC,CAAC;QAChE,OAAO;YACL,QAAQ,EAAE;gBACR;oBACE,GAAG,EAAE,GAAG,CAAC,IAAI;oBACb,QAAQ,EAAE,kBAAkB;oBAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;iBACnC;aACF;SACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
package/dist/server.d.ts
ADDED
package/dist/server.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { CaveauApi } from "./api.js";
|
|
3
|
+
import { registerSearch } from "./tools/search.js";
|
|
4
|
+
import { registerListProfiles } from "./tools/list_profiles.js";
|
|
5
|
+
import { registerListCorpora } from "./tools/list_corpora.js";
|
|
6
|
+
import { registerGetDocument } from "./tools/get_document.js";
|
|
7
|
+
import { registerCorpusStats } from "./tools/corpus_stats.js";
|
|
8
|
+
import { registerIngestText } from "./tools/ingest_text.js";
|
|
9
|
+
import { registerCorporaResource } from "./resources/corpora.js";
|
|
10
|
+
import { registerDocumentsResource } from "./resources/documents.js";
|
|
11
|
+
import { registerSummarizePrompt } from "./prompts/summarize.js";
|
|
12
|
+
import { registerDraftWithRefsPrompt } from "./prompts/draft_with_refs.js";
|
|
13
|
+
export function buildServer(opts) {
|
|
14
|
+
const server = new McpServer({
|
|
15
|
+
name: opts.name ?? "caveau-mcp",
|
|
16
|
+
version: opts.version ?? "0.2.2",
|
|
17
|
+
}, {
|
|
18
|
+
capabilities: {
|
|
19
|
+
tools: {},
|
|
20
|
+
resources: {},
|
|
21
|
+
prompts: {},
|
|
22
|
+
},
|
|
23
|
+
instructions: "CaveauAI MCP server. Tools: caveau.list_corpora (see queryable corpora), " +
|
|
24
|
+
"caveau.list_profiles (saved retrieval criteria), caveau.search (RAG search " +
|
|
25
|
+
"returning ranked chunks with source citations), caveau.get_document (full " +
|
|
26
|
+
"document by id), caveau.corpus_stats (counts + plan caps), caveau.ingest_text " +
|
|
27
|
+
"(add text to a private corpus, plan-gated). Resources: caveau://corpora, " +
|
|
28
|
+
"caveau://documents/{id}. Prompts: caveau.summarize_with_corpus, " +
|
|
29
|
+
"caveau.draft_with_refs. The active client and tenant scope are determined by " +
|
|
30
|
+
"the MCP token configured at startup.",
|
|
31
|
+
});
|
|
32
|
+
const api = new CaveauApi({
|
|
33
|
+
baseUrl: opts.apiBaseUrl,
|
|
34
|
+
token: opts.apiToken,
|
|
35
|
+
});
|
|
36
|
+
registerSearch(server, api);
|
|
37
|
+
registerListProfiles(server, api);
|
|
38
|
+
registerListCorpora(server, api);
|
|
39
|
+
registerGetDocument(server, api);
|
|
40
|
+
registerCorpusStats(server, api);
|
|
41
|
+
registerIngestText(server, api);
|
|
42
|
+
registerCorporaResource(server, api);
|
|
43
|
+
registerDocumentsResource(server, api);
|
|
44
|
+
registerSummarizePrompt(server);
|
|
45
|
+
registerDraftWithRefsPrompt(server);
|
|
46
|
+
return server;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,2BAA2B,EAAE,MAAM,8BAA8B,CAAC;AAS3E,MAAM,UAAU,WAAW,CAAC,IAAkB;IAC5C,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B;QACE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,YAAY;QAC/B,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,OAAO;KACjC,EACD;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;YACT,SAAS,EAAE,EAAE;YACb,OAAO,EAAE,EAAE;SACZ;QACD,YAAY,EACV,2EAA2E;YAC3E,6EAA6E;YAC7E,4EAA4E;YAC5E,gFAAgF;YAChF,2EAA2E;YAC3E,kEAAkE;YAClE,+EAA+E;YAC/E,sCAAsC;KACzC,CACF,CAAC;IAEF,MAAM,GAAG,GAAG,IAAI,SAAS,CAAC;QACxB,OAAO,EAAE,IAAI,CAAC,UAAU;QACxB,KAAK,EAAE,IAAI,CAAC,QAAQ;KACrB,CAAC,CAAC;IAEH,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,oBAAoB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAClC,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,mBAAmB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEhC,uBAAuB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACrC,yBAAyB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEvC,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAChC,2BAA2B,CAAC,MAAM,CAAC,CAAC;IAEpC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function registerCorpusStats(server, api) {
|
|
2
|
+
server.registerTool("caveau.corpus_stats", {
|
|
3
|
+
title: "Corpus stats",
|
|
4
|
+
description: "Show document and chunk counts for each of this client's private corpora, plus their plan caps. Useful for sanity-checking before a large query session.",
|
|
5
|
+
inputSchema: {},
|
|
6
|
+
}, async () => {
|
|
7
|
+
const res = await api.get("/corpus_stats");
|
|
8
|
+
const lines = [
|
|
9
|
+
`Total: ${res.totals.documents} documents, ${res.totals.chunks} chunks`,
|
|
10
|
+
`Plan: ${res.plan.mcp_calls_per_month.toLocaleString()} MCP calls/month, max ${res.plan.max_mcp_tools} tools`,
|
|
11
|
+
"",
|
|
12
|
+
"Per corpus:",
|
|
13
|
+
...res.corpora.map((c) => ` ${c.slug.padEnd(28)} ${String(c.documents).padStart(5)} docs / ${String(c.chunks).padStart(6)} chunks (${c.name})`),
|
|
14
|
+
];
|
|
15
|
+
return {
|
|
16
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
17
|
+
structuredContent: res,
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=corpus_stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"corpus_stats.js","sourceRoot":"","sources":["../../src/tools/corpus_stats.ts"],"names":[],"mappings":"AASA,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,GAAc;IACnE,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,0JAA0J;QAC5J,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE;QACT,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAc,eAAe,CAAC,CAAC;QACxD,MAAM,KAAK,GAAa;YACtB,UAAU,GAAG,CAAC,MAAM,CAAC,SAAS,eAAe,GAAG,CAAC,MAAM,CAAC,MAAM,SAAS;YACvE,SAAS,GAAG,CAAC,IAAI,CAAC,mBAAmB,CAAC,cAAc,EAAE,yBAAyB,GAAG,CAAC,IAAI,CAAC,aAAa,QAAQ;YAC7G,EAAE;YACF,aAAa;YACb,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAChB,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,IAAI,GAAG,CAC/H;SACF,CAAC;QACF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,iBAAiB,EAAE,GAAyC;SAC7D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerGetDocument(server, api) {
|
|
3
|
+
server.registerTool("caveau.get_document", {
|
|
4
|
+
title: "Get document",
|
|
5
|
+
description: "Fetch a single document by id along with its chunked content. Use this when caveau.search returns a chunk and you need surrounding context or the full source text.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
document_id: z
|
|
8
|
+
.number()
|
|
9
|
+
.int()
|
|
10
|
+
.positive()
|
|
11
|
+
.describe("The document_id field returned by caveau.search."),
|
|
12
|
+
},
|
|
13
|
+
}, async (args) => {
|
|
14
|
+
const res = await api.get(`/documents/${args.document_id}`);
|
|
15
|
+
const d = res.document;
|
|
16
|
+
const head = [
|
|
17
|
+
`# ${d.title ?? "(untitled)"}`,
|
|
18
|
+
d.author ? `Author: ${d.author}` : null,
|
|
19
|
+
d.published_at ? `Published: ${d.published_at}` : null,
|
|
20
|
+
d.category ? `Category: ${d.category}` : null,
|
|
21
|
+
d.source_url ? `Source: ${d.source_url}` : null,
|
|
22
|
+
`Chunks: ${res.chunks.length}`,
|
|
23
|
+
]
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.join("\n");
|
|
26
|
+
const body = res.chunks
|
|
27
|
+
.map((c) => `--- chunk ${c.chunk_index}${c.section_title ? ` (${c.section_title})` : ""} ---\n${c.content}`)
|
|
28
|
+
.join("\n\n");
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: `${head}\n\n${body}` }],
|
|
31
|
+
structuredContent: res,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=get_document.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get_document.js","sourceRoot":"","sources":["../../src/tools/get_document.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,GAAc;IACnE,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,qKAAqK;QACvK,WAAW,EAAE;YACX,WAAW,EAAE,CAAC;iBACX,MAAM,EAAE;iBACR,GAAG,EAAE;iBACL,QAAQ,EAAE;iBACV,QAAQ,CAAC,kDAAkD,CAAC;SAChE;KACF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAmB,cAAc,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC9E,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC;QACvB,MAAM,IAAI,GAAG;YACX,KAAK,CAAC,CAAC,KAAK,IAAI,YAAY,EAAE;YAC9B,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,IAAI;YACvC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,IAAI;YACtD,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI;YAC7C,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI;YAC/C,WAAW,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE;SAC/B;aACE,MAAM,CAAC,OAAO,CAAC;aACf,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,IAAI,GAAG,GAAG,CAAC,MAAM;aACpB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,WAAW,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;aAC3G,IAAI,CAAC,MAAM,CAAC,CAAC;QAChB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;YACvD,iBAAiB,EAAE,GAAyC;SAC7D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export function registerIngestText(server, api) {
|
|
3
|
+
server.registerTool("caveau.ingest_text", {
|
|
4
|
+
title: "Ingest text",
|
|
5
|
+
description: "Add a piece of text to one of this client's private corpora. Chunks, embeds, and stores in Qdrant. Plan-gated server-side; returns the resulting document id and chunk count.",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
title: z.string().min(1).max(500).describe("Document title."),
|
|
8
|
+
content: z
|
|
9
|
+
.string()
|
|
10
|
+
.min(30)
|
|
11
|
+
.max(2_000_000)
|
|
12
|
+
.describe("Document body. 30 chars min, 2 MB max."),
|
|
13
|
+
corpus: z
|
|
14
|
+
.string()
|
|
15
|
+
.max(100)
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Corpus slug. Omit to use the client's default corpus."),
|
|
18
|
+
category: z.string().max(120).optional(),
|
|
19
|
+
tags: z.array(z.string()).optional(),
|
|
20
|
+
source_url: z.string().url().optional(),
|
|
21
|
+
},
|
|
22
|
+
}, async (args) => {
|
|
23
|
+
const res = await api.post("/ingest", {
|
|
24
|
+
title: args.title,
|
|
25
|
+
content: args.content,
|
|
26
|
+
corpus: args.corpus,
|
|
27
|
+
category: args.category,
|
|
28
|
+
tags: args.tags,
|
|
29
|
+
source_url: args.source_url,
|
|
30
|
+
});
|
|
31
|
+
const d = res.document;
|
|
32
|
+
return {
|
|
33
|
+
content: [
|
|
34
|
+
{
|
|
35
|
+
type: "text",
|
|
36
|
+
text: `Ingested "${d.title}" (id ${d.id}) into corpus "${d.corpus_slug ?? "default"}" — ${d.chunks} chunks indexed.`,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
structuredContent: res,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=ingest_text.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest_text.js","sourceRoot":"","sources":["../../src/tools/ingest_text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAcxB,MAAM,UAAU,kBAAkB,CAAC,MAAiB,EAAE,GAAc;IAClE,MAAM,CAAC,YAAY,CACjB,oBAAoB,EACpB;QACE,KAAK,EAAE,aAAa;QACpB,WAAW,EACT,+KAA+K;QACjL,WAAW,EAAE;YACX,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,iBAAiB,CAAC;YAC7D,OAAO,EAAE,CAAC;iBACP,MAAM,EAAE;iBACR,GAAG,CAAC,EAAE,CAAC;iBACP,GAAG,CAAC,SAAS,CAAC;iBACd,QAAQ,CAAC,wCAAwC,CAAC;YACrD,MAAM,EAAE,CAAC;iBACN,MAAM,EAAE;iBACR,GAAG,CAAC,GAAG,CAAC;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,uDAAuD,CAAC;YACpE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;YACxC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;YACpC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SACxC;KACF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAiB,SAAS,EAAE;YACpD,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC;QACvB,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,aAAa,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC,WAAW,IAAI,SAAS,OAAO,CAAC,CAAC,MAAM,kBAAkB;iBACrH;aACF;YACD,iBAAiB,EAAE,GAAyC;SAC7D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function registerListCorpora(server, api) {
|
|
2
|
+
server.registerTool("caveau.list_corpora", {
|
|
3
|
+
title: "List corpora",
|
|
4
|
+
description: "List all corpora this client can search: their private corpora and any shared/subscribed packages. Use the slugs in corpus_slugs[] when configuring a retrieval profile.",
|
|
5
|
+
inputSchema: {},
|
|
6
|
+
}, async () => {
|
|
7
|
+
const res = await api.get("/corpora");
|
|
8
|
+
const lines = [];
|
|
9
|
+
lines.push(`Private (${res.private.length}):`);
|
|
10
|
+
for (const c of res.private) {
|
|
11
|
+
lines.push(` • ${c.slug} — ${c.name}` +
|
|
12
|
+
(c.is_default ? " [default]" : "") +
|
|
13
|
+
` — ${c.doc_count} docs` +
|
|
14
|
+
(c.description ? `\n ${c.description}` : ""));
|
|
15
|
+
}
|
|
16
|
+
lines.push("");
|
|
17
|
+
lines.push(`Subscribed shared packages (${res.subscribed.length}):`);
|
|
18
|
+
for (const c of res.subscribed) {
|
|
19
|
+
lines.push(` • ${c.slug} — ${c.name} — ${c.doc_count} docs / ${c.chunk_count ?? 0} chunks` +
|
|
20
|
+
(c.description ? `\n ${c.description}` : ""));
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
24
|
+
structuredContent: res,
|
|
25
|
+
};
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=list_corpora.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list_corpora.js","sourceRoot":"","sources":["../../src/tools/list_corpora.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,GAAc;IACnE,MAAM,CAAC,YAAY,CACjB,qBAAqB,EACrB;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EACT,0KAA0K;QAC5K,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE;QACT,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAkB,UAAU,CAAC,CAAC;QACvD,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC,YAAY,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;QAC/C,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CACR,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,EAAE;gBACzB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClC,MAAM,CAAC,CAAC,SAAS,OAAO;gBACxB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpD,CAAC;QACJ,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,+BAA+B,GAAG,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,CAAC;QACrE,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,UAAU,EAAE,CAAC;YAC/B,KAAK,CAAC,IAAI,CACR,OAAO,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,SAAS,WAAW,CAAC,CAAC,WAAW,IAAI,CAAC,SAAS;gBAC9E,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CACpD,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,iBAAiB,EAAE,GAAyC;SAC7D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function registerListProfiles(server, api) {
|
|
2
|
+
server.registerTool("caveau.list_profiles", {
|
|
3
|
+
title: "List retrieval profiles",
|
|
4
|
+
description: "List the named retrieval profiles configured for this client. Each profile is a saved bundle of corpora, search method, top_k, and filters. Pass a profile slug to caveau.search to use it.",
|
|
5
|
+
inputSchema: {},
|
|
6
|
+
}, async () => {
|
|
7
|
+
const res = await api.get("/profiles");
|
|
8
|
+
const lines = [`${res.count} profile${res.count === 1 ? "" : "s"}`];
|
|
9
|
+
for (const p of res.profiles) {
|
|
10
|
+
const tags = [];
|
|
11
|
+
tags.push(p.search_method);
|
|
12
|
+
tags.push(`top_k=${p.top_k}`);
|
|
13
|
+
if (p.recency_days !== null)
|
|
14
|
+
tags.push(`recency=${p.recency_days}d`);
|
|
15
|
+
if (p.corpus_slugs && p.corpus_slugs.length > 0)
|
|
16
|
+
tags.push(`corpora=${p.corpus_slugs.join(",")}`);
|
|
17
|
+
if (p.categories && p.categories.length > 0)
|
|
18
|
+
tags.push(`categories=${p.categories.join(",")}`);
|
|
19
|
+
if (p.is_default)
|
|
20
|
+
tags.push("DEFAULT");
|
|
21
|
+
lines.push(`• ${p.slug} (${p.name}) — ${tags.join(" / ")}` +
|
|
22
|
+
(p.description ? `\n ${p.description}` : ""));
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
content: [{ type: "text", text: lines.join("\n") }],
|
|
26
|
+
structuredContent: res,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=list_profiles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list_profiles.js","sourceRoot":"","sources":["../../src/tools/list_profiles.ts"],"names":[],"mappings":"AAIA,MAAM,UAAU,oBAAoB,CAAC,MAAiB,EAAE,GAAc;IACpE,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;QACE,KAAK,EAAE,yBAAyB;QAChC,WAAW,EACT,6LAA6L;QAC/L,WAAW,EAAE,EAAE;KAChB,EACD,KAAK,IAAI,EAAE;QACT,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,GAAG,CAAmB,WAAW,CAAC,CAAC;QACzD,MAAM,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC,KAAK,WAAW,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC;QACpE,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAa,EAAE,CAAC;YAC1B,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;YAC9B,IAAI,CAAC,CAAC,YAAY,KAAK,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC;YACrE,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC;gBAC7C,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACnD,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC;gBACzC,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACpD,IAAI,CAAC,CAAC,UAAU;gBAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YACvC,KAAK,CAAC,IAAI,CACR,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;gBAC7C,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAClD,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,iBAAiB,EAAE,GAAyC;SAC7D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const Input = {
|
|
3
|
+
query: z.string().min(1).max(4000).describe("The user's question or search phrase."),
|
|
4
|
+
profile: z
|
|
5
|
+
.string()
|
|
6
|
+
.max(64)
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Slug of a saved retrieval profile (corpora, top_k, filters). Omit to use the client's default profile."),
|
|
9
|
+
top_k: z
|
|
10
|
+
.number()
|
|
11
|
+
.int()
|
|
12
|
+
.min(1)
|
|
13
|
+
.max(50)
|
|
14
|
+
.optional()
|
|
15
|
+
.describe("Override the profile's top_k. Bounded 1-50."),
|
|
16
|
+
category: z
|
|
17
|
+
.string()
|
|
18
|
+
.max(120)
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("One-shot category filter applied on top of any profile filters."),
|
|
21
|
+
};
|
|
22
|
+
export function registerSearch(server, api) {
|
|
23
|
+
server.registerTool("caveau.search", {
|
|
24
|
+
title: "Search corpus",
|
|
25
|
+
description: "Run a RAG search across the client's accessible corpora and return ranked chunks with source metadata. Use the result chunks as context for your reply; cite source_url and title.",
|
|
26
|
+
inputSchema: Input,
|
|
27
|
+
}, async (args) => {
|
|
28
|
+
const res = await api.post("/search", {
|
|
29
|
+
query: args.query,
|
|
30
|
+
profile: args.profile,
|
|
31
|
+
top_k: args.top_k,
|
|
32
|
+
category: args.category,
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: res.results.length === 0
|
|
39
|
+
? `No matches for "${args.query}" under profile "${res.profile}".`
|
|
40
|
+
: formatHits(res),
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
structuredContent: res,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function formatHits(res) {
|
|
48
|
+
const lines = [
|
|
49
|
+
`Profile: ${res.profile} — ${res.count} result${res.count === 1 ? "" : "s"}`,
|
|
50
|
+
"",
|
|
51
|
+
];
|
|
52
|
+
for (const [i, h] of res.results.entries()) {
|
|
53
|
+
const head = `[${i + 1}] ${h.title ?? "(untitled)"}` +
|
|
54
|
+
(h.section_title ? ` — ${h.section_title}` : "") +
|
|
55
|
+
(h.score !== null ? ` (score ${h.score.toFixed(3)})` : "");
|
|
56
|
+
lines.push(head);
|
|
57
|
+
if (h.source_url)
|
|
58
|
+
lines.push(` ${h.source_url}`);
|
|
59
|
+
lines.push(indent(h.content.trim()));
|
|
60
|
+
lines.push("");
|
|
61
|
+
}
|
|
62
|
+
return lines.join("\n").trimEnd();
|
|
63
|
+
}
|
|
64
|
+
function indent(s) {
|
|
65
|
+
return s
|
|
66
|
+
.split("\n")
|
|
67
|
+
.map((l) => ` ${l}`)
|
|
68
|
+
.join("\n");
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,MAAM,KAAK,GAAG;IACZ,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,uCAAuC,CAAC;IACpF,OAAO,EAAE,CAAC;SACP,MAAM,EAAE;SACR,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CACP,wGAAwG,CACzG;IACH,KAAK,EAAE,CAAC;SACL,MAAM,EAAE;SACR,GAAG,EAAE;SACL,GAAG,CAAC,CAAC,CAAC;SACN,GAAG,CAAC,EAAE,CAAC;SACP,QAAQ,EAAE;SACV,QAAQ,CAAC,6CAA6C,CAAC;IAC1D,QAAQ,EAAE,CAAC;SACR,MAAM,EAAE;SACR,GAAG,CAAC,GAAG,CAAC;SACR,QAAQ,EAAE;SACV,QAAQ,CAAC,iEAAiE,CAAC;CAC/E,CAAC;AAEF,MAAM,UAAU,cAAc,CAAC,MAAiB,EAAE,GAAc;IAC9D,MAAM,CAAC,YAAY,CACjB,eAAe,EACf;QACE,KAAK,EAAE,eAAe;QACtB,WAAW,EACT,oLAAoL;QACtL,WAAW,EAAE,KAAK;KACnB,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAiB,SAAS,EAAE;YACpD,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;QACH,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EACF,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;wBACtB,CAAC,CAAC,mBAAmB,IAAI,CAAC,KAAK,oBAAoB,GAAG,CAAC,OAAO,IAAI;wBAClE,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;iBACtB;aACF;YACD,iBAAiB,EAAE,GAAyC;SAC7D,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,GAAmB;IACrC,MAAM,KAAK,GAAa;QACtB,YAAY,GAAG,CAAC,OAAO,MAAM,GAAG,CAAC,KAAK,UAAU,GAAG,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE;QAC5E,EAAE;KACH,CAAC;IACF,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,GACR,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,YAAY,EAAE;YACvC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAChD,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7D,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,IAAI,CAAC,CAAC,UAAU;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC;QACpD,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;QACrC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,MAAM,CAAC,CAAS;IACvB,OAAO,CAAC;SACL,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;SACtB,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire types for /api/mcp/* responses. These mirror what the PHP runtime
|
|
3
|
+
* controller returns (see ai-portal/api/mcp/index.php).
|
|
4
|
+
*/
|
|
5
|
+
export interface SearchHit {
|
|
6
|
+
chunk_id: string | number | null;
|
|
7
|
+
document_id: string | number | null;
|
|
8
|
+
title: string | null;
|
|
9
|
+
content: string;
|
|
10
|
+
section_title: string | null;
|
|
11
|
+
category: string | null;
|
|
12
|
+
tags: string[] | null;
|
|
13
|
+
source_url: string | null;
|
|
14
|
+
score: number | null;
|
|
15
|
+
corpus_slug: string | null;
|
|
16
|
+
published_at: string | null;
|
|
17
|
+
}
|
|
18
|
+
export interface SearchResponse {
|
|
19
|
+
profile: string;
|
|
20
|
+
count: number;
|
|
21
|
+
results: SearchHit[];
|
|
22
|
+
}
|
|
23
|
+
export interface Corpus {
|
|
24
|
+
kind: "private" | "shared";
|
|
25
|
+
slug: string;
|
|
26
|
+
name: string;
|
|
27
|
+
description: string | null;
|
|
28
|
+
doc_count: number;
|
|
29
|
+
chunk_count?: number;
|
|
30
|
+
is_default?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface CorporaResponse {
|
|
33
|
+
private: Corpus[];
|
|
34
|
+
subscribed: Corpus[];
|
|
35
|
+
}
|
|
36
|
+
export interface Profile {
|
|
37
|
+
slug: string;
|
|
38
|
+
name: string;
|
|
39
|
+
description: string | null;
|
|
40
|
+
search_method: "vector" | "keyword" | "hybrid";
|
|
41
|
+
corpus_slugs: string[] | null;
|
|
42
|
+
top_k: number;
|
|
43
|
+
recency_days: number | null;
|
|
44
|
+
categories: string[] | null;
|
|
45
|
+
tags: string[] | null;
|
|
46
|
+
embed_model: string | null;
|
|
47
|
+
is_default: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface ProfilesResponse {
|
|
50
|
+
profiles: Profile[];
|
|
51
|
+
count: number;
|
|
52
|
+
}
|
|
53
|
+
export interface DocumentChunk {
|
|
54
|
+
id: number;
|
|
55
|
+
chunk_index: number;
|
|
56
|
+
section_title: string | null;
|
|
57
|
+
content: string;
|
|
58
|
+
}
|
|
59
|
+
export interface DocumentResponse {
|
|
60
|
+
document: {
|
|
61
|
+
id: number;
|
|
62
|
+
title: string | null;
|
|
63
|
+
category: string | null;
|
|
64
|
+
tags: string[] | null;
|
|
65
|
+
author: string | null;
|
|
66
|
+
source_url: string | null;
|
|
67
|
+
published_at: string | null;
|
|
68
|
+
status: string;
|
|
69
|
+
corpus_id: number | null;
|
|
70
|
+
chunk_count: number;
|
|
71
|
+
content: string | null;
|
|
72
|
+
created_at: string;
|
|
73
|
+
updated_at: string;
|
|
74
|
+
};
|
|
75
|
+
chunks: DocumentChunk[];
|
|
76
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bluenotelogic/mcp",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "CaveauAI MCP server — RAG context injection for Claude Code/Desktop and other MCP hosts. Each client uses their own token + retrieval profiles.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"caveau-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"dev": "tsx watch src/index.ts --stdio",
|
|
17
|
+
"start": "node dist/index.js --stdio",
|
|
18
|
+
"start:http": "node dist/index.js --http",
|
|
19
|
+
"lint": "tsc --noEmit",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
27
|
+
"express": "^5.2.1",
|
|
28
|
+
"zod": "^3.23.8"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/express": "^5.0.6",
|
|
32
|
+
"@types/node": "^22.0.0",
|
|
33
|
+
"tsx": "^4.19.0",
|
|
34
|
+
"typescript": "^5.6.0"
|
|
35
|
+
},
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://git.bluenotelogic.com/daveadmin/caveau-mcp"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"mcp",
|
|
45
|
+
"model-context-protocol",
|
|
46
|
+
"rag",
|
|
47
|
+
"caveauai",
|
|
48
|
+
"claude"
|
|
49
|
+
],
|
|
50
|
+
"license": "UNLICENSED",
|
|
51
|
+
"author": "Blue Note Logic"
|
|
52
|
+
}
|