@gloriginmind/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/README.md +95 -0
- package/dist/auth.d.ts +18 -0
- package/dist/auth.js +69 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +91 -0
- package/dist/http.d.ts +2 -0
- package/dist/http.js +84 -0
- package/dist/stdio.d.ts +2 -0
- package/dist/stdio.js +12 -0
- package/dist/tools.d.ts +181 -0
- package/dist/tools.js +176 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# OriginMind MCP
|
|
2
|
+
|
|
3
|
+
MCP server for OriginMind dual-auth skill routes. Auth is the same Bootstrap OAuth path as skills (`ORIGINMIND_API_KEY` → `~/.originmind/.env`). No MCP-specific OAuth.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Run Bootstrap OAuth once on the machine:
|
|
8
|
+
|
|
9
|
+
```powershell
|
|
10
|
+
. ./skills/create/scripts/setup-oauth.ps1
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
source ./skills/create/scripts/setup-oauth.sh
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Key is stored in `~/.originmind/.env`. Do **not** paste the key into `mcp.json`.
|
|
18
|
+
|
|
19
|
+
## Cursor (`mcp.json`)
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"mcpServers": {
|
|
24
|
+
"originmind": {
|
|
25
|
+
"command": "npx",
|
|
26
|
+
"args": ["-y", "@gloriginmind/mcp"],
|
|
27
|
+
"env": {}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Local checkout (this monorepo):
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"originmind": {
|
|
39
|
+
"command": "node",
|
|
40
|
+
"args": ["path/to/OriAI_orchestrator/skills/mcp/dist/stdio.js"],
|
|
41
|
+
"env": {}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If the key is missing, tools return a `MISSING_KEY` error pointing at OAuth setup.
|
|
48
|
+
|
|
49
|
+
## Transports
|
|
50
|
+
|
|
51
|
+
| Transport | Entry | Use |
|
|
52
|
+
|-----------|-------|-----|
|
|
53
|
+
| **stdio** | `originmind-mcp` / `dist/stdio.js` | Cursor |
|
|
54
|
+
| **HTTP** | `originmind-mcp-http` / `dist/http.js` | LiveKit `MCPServerHTTP` (future) |
|
|
55
|
+
|
|
56
|
+
HTTP defaults: `ORIGINMIND_MCP_HOST=127.0.0.1`, `ORIGINMIND_MCP_PORT=3920`, path `/mcp`, health `GET /health`.
|
|
57
|
+
|
|
58
|
+
Per-request auth: prefer `Authorization: Bearer <session JWT>`; else fall back to `ORIGINMIND_API_KEY`.
|
|
59
|
+
|
|
60
|
+
### LiveKit (documented, not wired this pass)
|
|
61
|
+
|
|
62
|
+
When the HTTP server is running:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from livekit.agents import mcp
|
|
66
|
+
|
|
67
|
+
mcp.MCPServerHTTP(
|
|
68
|
+
url=f"http://127.0.0.1:{port}/mcp",
|
|
69
|
+
headers={"Authorization": f"Bearer {jwt}"},
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Do not attach alongside the existing `@function_tool`s until you choose which surface wins (avoids duplicate tools).
|
|
74
|
+
|
|
75
|
+
## Tools
|
|
76
|
+
|
|
77
|
+
**Read:** `list_projects`, `search_creative_dna`, `list_conversations`, `list_messages`, `get_transcript`
|
|
78
|
+
**Tasks:** `create_task`, `list_tasks`, `get_task`, `patch_task`
|
|
79
|
+
**Creations:** `list_creations`, `get_creation`, `create_creation`, `update_creation`
|
|
80
|
+
**References:** `list_references`, `create_reference`
|
|
81
|
+
|
|
82
|
+
Same routes as [search-creative-dna/references/routes.md](../search-creative-dna/references/routes.md).
|
|
83
|
+
|
|
84
|
+
**Creations** use the creations API (`list_creations` / `get_creation` / `create_creation` / `update_creation`). Notion tools are **not** exposed here (parked).
|
|
85
|
+
|
|
86
|
+
## Dev
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cd skills/mcp
|
|
90
|
+
npm install # may need corporate TLS workaround
|
|
91
|
+
npm run build
|
|
92
|
+
npm test
|
|
93
|
+
npm start # stdio
|
|
94
|
+
npm run start:http # HTTP
|
|
95
|
+
```
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type AuthCred = {
|
|
2
|
+
kind: "api_key" | "bearer";
|
|
3
|
+
value: string;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* Resolve auth for MCP → OriginMind API.
|
|
7
|
+
* Prefer explicit Bearer (HTTP/voice), else ORIGINMIND_API_KEY from env / ~/.originmind/.env / repo .env.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveAuth(options?: {
|
|
10
|
+
authorizationHeader?: string | null;
|
|
11
|
+
cwd?: string;
|
|
12
|
+
/** Override machine env path; pass `null` to skip (~/.originmind/.env). */
|
|
13
|
+
machineEnvPath?: string | null;
|
|
14
|
+
}): AuthCred | {
|
|
15
|
+
error: "MISSING_KEY";
|
|
16
|
+
};
|
|
17
|
+
export declare function authHeaders(cred: AuthCred): Record<string, string>;
|
|
18
|
+
export declare const MISSING_KEY_MESSAGE = "MISSING_KEY: Run OriginMind Bootstrap OAuth first \u2014 . ./skills/create/scripts/setup-oauth.ps1 (or .sh). Key is saved to ~/.originmind/.env.";
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
function readKeyFromFile(filePath) {
|
|
5
|
+
if (!fs.existsSync(filePath))
|
|
6
|
+
return null;
|
|
7
|
+
const lines = fs.readFileSync(filePath, "utf8").split(/\r?\n/);
|
|
8
|
+
for (const line of lines) {
|
|
9
|
+
const m = line.match(/^\s*ORIGINMIND_API_KEY\s*=\s*(.+)\s*$/);
|
|
10
|
+
if (m) {
|
|
11
|
+
return m[1].trim().replace(/^["']|["']$/g, "");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
function findRepoRoot(start) {
|
|
17
|
+
let dir = start;
|
|
18
|
+
while (dir) {
|
|
19
|
+
for (const marker of [".env", ".env.local", ".git"]) {
|
|
20
|
+
if (fs.existsSync(path.join(dir, marker)))
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
const parent = path.dirname(dir);
|
|
24
|
+
if (parent === dir)
|
|
25
|
+
break;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
return start;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Resolve auth for MCP → OriginMind API.
|
|
32
|
+
* Prefer explicit Bearer (HTTP/voice), else ORIGINMIND_API_KEY from env / ~/.originmind/.env / repo .env.
|
|
33
|
+
*/
|
|
34
|
+
export function resolveAuth(options) {
|
|
35
|
+
const header = options?.authorizationHeader?.trim();
|
|
36
|
+
if (header) {
|
|
37
|
+
const bearer = header.match(/^Bearer\s+(.+)$/i);
|
|
38
|
+
if (bearer?.[1]) {
|
|
39
|
+
return { kind: "bearer", value: bearer[1].trim() };
|
|
40
|
+
}
|
|
41
|
+
if (header.startsWith("om_live_")) {
|
|
42
|
+
return { kind: "api_key", value: header };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (process.env.ORIGINMIND_API_KEY?.trim()) {
|
|
46
|
+
return { kind: "api_key", value: process.env.ORIGINMIND_API_KEY.trim() };
|
|
47
|
+
}
|
|
48
|
+
if (options?.machineEnvPath !== null) {
|
|
49
|
+
const machinePath = options?.machineEnvPath ?? path.join(os.homedir(), ".originmind", ".env");
|
|
50
|
+
const machineKey = readKeyFromFile(machinePath);
|
|
51
|
+
if (machineKey) {
|
|
52
|
+
return { kind: "api_key", value: machineKey };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const root = findRepoRoot(options?.cwd ?? process.cwd());
|
|
56
|
+
for (const name of [".env", ".env.local"]) {
|
|
57
|
+
const key = readKeyFromFile(path.join(root, name));
|
|
58
|
+
if (key)
|
|
59
|
+
return { kind: "api_key", value: key };
|
|
60
|
+
}
|
|
61
|
+
return { error: "MISSING_KEY" };
|
|
62
|
+
}
|
|
63
|
+
export function authHeaders(cred) {
|
|
64
|
+
if (cred.kind === "bearer") {
|
|
65
|
+
return { Authorization: `Bearer ${cred.value}` };
|
|
66
|
+
}
|
|
67
|
+
return { "X-OriAI-Api-Key": cred.value };
|
|
68
|
+
}
|
|
69
|
+
export const MISSING_KEY_MESSAGE = "MISSING_KEY: Run OriginMind Bootstrap OAuth first — . ./skills/create/scripts/setup-oauth.ps1 (or .sh). Key is saved to ~/.originmind/.env.";
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type AuthCred } from "./auth.js";
|
|
2
|
+
export declare const DEFAULT_BASE_URL = "https://api.originmind.ai";
|
|
3
|
+
export declare class ApiError extends Error {
|
|
4
|
+
status: number;
|
|
5
|
+
body: string;
|
|
6
|
+
constructor(message: string, status: number, body: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class OriginMindClient {
|
|
9
|
+
private cred;
|
|
10
|
+
private baseUrl;
|
|
11
|
+
constructor(cred: AuthCred, baseUrl?: string);
|
|
12
|
+
request<T = unknown>(method: string, path: string, options?: {
|
|
13
|
+
query?: Record<string, string | undefined>;
|
|
14
|
+
body?: unknown;
|
|
15
|
+
}): Promise<T>;
|
|
16
|
+
get<T = unknown>(path: string, query?: Record<string, string | undefined>): Promise<T>;
|
|
17
|
+
post<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
18
|
+
patch<T = unknown>(path: string, body?: unknown): Promise<T>;
|
|
19
|
+
postMultipart<T = unknown>(path: string, options: {
|
|
20
|
+
fields: Record<string, string>;
|
|
21
|
+
file: {
|
|
22
|
+
fieldName: string;
|
|
23
|
+
filename: string;
|
|
24
|
+
contentType: string;
|
|
25
|
+
body: string;
|
|
26
|
+
};
|
|
27
|
+
}): Promise<T>;
|
|
28
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { authHeaders } from "./auth.js";
|
|
2
|
+
export const DEFAULT_BASE_URL = "https://api.originmind.ai";
|
|
3
|
+
export class ApiError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
body;
|
|
6
|
+
constructor(message, status, body) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.body = body;
|
|
10
|
+
this.name = "ApiError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class OriginMindClient {
|
|
14
|
+
cred;
|
|
15
|
+
baseUrl;
|
|
16
|
+
constructor(cred, baseUrl = process.env.ORIGINMIND_API_BASE_URL?.replace(/\/$/, "") || DEFAULT_BASE_URL) {
|
|
17
|
+
this.cred = cred;
|
|
18
|
+
this.baseUrl = baseUrl;
|
|
19
|
+
}
|
|
20
|
+
async request(method, path, options) {
|
|
21
|
+
const url = new URL(path.startsWith("http") ? path : `${this.baseUrl}${path}`);
|
|
22
|
+
if (options?.query) {
|
|
23
|
+
for (const [k, v] of Object.entries(options.query)) {
|
|
24
|
+
if (v !== undefined && v !== "")
|
|
25
|
+
url.searchParams.set(k, v);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const headers = {
|
|
29
|
+
Accept: "application/json",
|
|
30
|
+
...authHeaders(this.cred),
|
|
31
|
+
};
|
|
32
|
+
if (options?.body !== undefined) {
|
|
33
|
+
headers["Content-Type"] = "application/json";
|
|
34
|
+
}
|
|
35
|
+
const res = await fetch(url, {
|
|
36
|
+
method,
|
|
37
|
+
headers,
|
|
38
|
+
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
39
|
+
});
|
|
40
|
+
const text = await res.text();
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
throw new ApiError(`OriginMind API ${method} ${url.pathname}: ${res.status}`, res.status, text);
|
|
43
|
+
}
|
|
44
|
+
if (!text)
|
|
45
|
+
return undefined;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(text);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return text;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
get(path, query) {
|
|
54
|
+
return this.request("GET", path, { query });
|
|
55
|
+
}
|
|
56
|
+
post(path, body) {
|
|
57
|
+
return this.request("POST", path, { body });
|
|
58
|
+
}
|
|
59
|
+
patch(path, body) {
|
|
60
|
+
return this.request("PATCH", path, { body });
|
|
61
|
+
}
|
|
62
|
+
async postMultipart(path, options) {
|
|
63
|
+
const url = new URL(path.startsWith("http") ? path : `${this.baseUrl}${path}`);
|
|
64
|
+
const form = new FormData();
|
|
65
|
+
for (const [k, v] of Object.entries(options.fields)) {
|
|
66
|
+
form.append(k, v);
|
|
67
|
+
}
|
|
68
|
+
const blob = new Blob([options.file.body], { type: options.file.contentType });
|
|
69
|
+
form.append(options.file.fieldName, blob, options.file.filename);
|
|
70
|
+
const res = await fetch(url, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
Accept: "application/json",
|
|
74
|
+
...authHeaders(this.cred),
|
|
75
|
+
},
|
|
76
|
+
body: form,
|
|
77
|
+
});
|
|
78
|
+
const text = await res.text();
|
|
79
|
+
if (!res.ok) {
|
|
80
|
+
throw new ApiError(`OriginMind API POST ${url.pathname}: ${res.status}`, res.status, text);
|
|
81
|
+
}
|
|
82
|
+
if (!text)
|
|
83
|
+
return undefined;
|
|
84
|
+
try {
|
|
85
|
+
return JSON.parse(text);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return text;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
package/dist/http.d.ts
ADDED
package/dist/http.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Streamable HTTP MCP for LiveKit Agents (MCPServerHTTP) and other HTTP clients.
|
|
4
|
+
* Auth: prefer Authorization Bearer on each request; else ORIGINMIND_API_KEY / ~/.originmind/.env.
|
|
5
|
+
*
|
|
6
|
+
* Voice wiring is deferred — run this process and point LiveKit at:
|
|
7
|
+
* MCPServerHTTP(url=f"http://127.0.0.1:{port}/mcp", headers={"Authorization": f"Bearer {jwt}"})
|
|
8
|
+
*/
|
|
9
|
+
import { createServer } from "node:http";
|
|
10
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
11
|
+
import { createServer as createMcpServer } from "./tools.js";
|
|
12
|
+
const port = Number(process.env.ORIGINMIND_MCP_PORT || 3920);
|
|
13
|
+
const host = process.env.ORIGINMIND_MCP_HOST || "127.0.0.1";
|
|
14
|
+
function readJsonBody(req) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const chunks = [];
|
|
17
|
+
req.on("data", (c) => chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c)));
|
|
18
|
+
req.on("end", () => {
|
|
19
|
+
if (chunks.length === 0) {
|
|
20
|
+
resolve(undefined);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString("utf8")));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
reject(err);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
req.on("error", reject);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async function handleMcp(req, res) {
|
|
34
|
+
const authHeader = typeof req.headers.authorization === "string" ? req.headers.authorization : null;
|
|
35
|
+
const server = createMcpServer({
|
|
36
|
+
getAuthorizationHeader: () => authHeader,
|
|
37
|
+
});
|
|
38
|
+
const transport = new StreamableHTTPServerTransport({
|
|
39
|
+
sessionIdGenerator: undefined,
|
|
40
|
+
});
|
|
41
|
+
res.on("close", () => {
|
|
42
|
+
void transport.close();
|
|
43
|
+
void server.close();
|
|
44
|
+
});
|
|
45
|
+
try {
|
|
46
|
+
await server.connect(transport);
|
|
47
|
+
const body = req.method === "POST" ? await readJsonBody(req) : undefined;
|
|
48
|
+
await transport.handleRequest(req, res, body);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error("MCP HTTP error:", err);
|
|
52
|
+
if (!res.headersSent) {
|
|
53
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
54
|
+
res.end(JSON.stringify({
|
|
55
|
+
jsonrpc: "2.0",
|
|
56
|
+
error: { code: -32603, message: "Internal server error" },
|
|
57
|
+
id: null,
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function main() {
|
|
63
|
+
const httpServer = createServer(async (req, res) => {
|
|
64
|
+
const url = new URL(req.url || "/", `http://${host}:${port}`);
|
|
65
|
+
if (req.method === "GET" && url.pathname === "/health") {
|
|
66
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
67
|
+
res.end(JSON.stringify({ ok: true, service: "originmind-mcp" }));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (url.pathname === "/mcp" || url.pathname === "/") {
|
|
71
|
+
await handleMcp(req, res);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
75
|
+
res.end(JSON.stringify({ error: "not_found" }));
|
|
76
|
+
});
|
|
77
|
+
httpServer.listen(port, host, () => {
|
|
78
|
+
console.error(`OriginMind MCP HTTP listening on http://${host}:${port}/mcp (health: /health)`);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
main().catch((err) => {
|
|
82
|
+
console.error(err);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|
package/dist/stdio.d.ts
ADDED
package/dist/stdio.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { createServer } from "./tools.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
const server = createServer();
|
|
6
|
+
const transport = new StdioServerTransport();
|
|
7
|
+
await server.connect(transport);
|
|
8
|
+
}
|
|
9
|
+
main().catch((err) => {
|
|
10
|
+
console.error(err);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
package/dist/tools.d.ts
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export type ToolAuthContext = {
|
|
4
|
+
/** Per-request Authorization header (HTTP transport). */
|
|
5
|
+
getAuthorizationHeader?: () => string | null | undefined;
|
|
6
|
+
};
|
|
7
|
+
/** Zod shapes exported for unit tests. */
|
|
8
|
+
export declare const toolSchemas: {
|
|
9
|
+
list_projects: z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>;
|
|
10
|
+
search_creative_dna: z.ZodObject<{
|
|
11
|
+
project_id: z.ZodString;
|
|
12
|
+
query: z.ZodString;
|
|
13
|
+
vectorK: z.ZodOptional<z.ZodNumber>;
|
|
14
|
+
neighborLimit: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
}, "strip", z.ZodTypeAny, {
|
|
16
|
+
query: string;
|
|
17
|
+
project_id: string;
|
|
18
|
+
vectorK?: number | undefined;
|
|
19
|
+
neighborLimit?: number | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
query: string;
|
|
22
|
+
project_id: string;
|
|
23
|
+
vectorK?: number | undefined;
|
|
24
|
+
neighborLimit?: number | undefined;
|
|
25
|
+
}>;
|
|
26
|
+
list_conversations: z.ZodObject<{
|
|
27
|
+
project_id: z.ZodString;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
project_id: string;
|
|
30
|
+
}, {
|
|
31
|
+
project_id: string;
|
|
32
|
+
}>;
|
|
33
|
+
list_messages: z.ZodObject<{
|
|
34
|
+
conversation_id: z.ZodString;
|
|
35
|
+
}, "strip", z.ZodTypeAny, {
|
|
36
|
+
conversation_id: string;
|
|
37
|
+
}, {
|
|
38
|
+
conversation_id: string;
|
|
39
|
+
}>;
|
|
40
|
+
get_transcript: z.ZodObject<{
|
|
41
|
+
conversation_id: z.ZodString;
|
|
42
|
+
}, "strip", z.ZodTypeAny, {
|
|
43
|
+
conversation_id: string;
|
|
44
|
+
}, {
|
|
45
|
+
conversation_id: string;
|
|
46
|
+
}>;
|
|
47
|
+
create_task: z.ZodObject<{
|
|
48
|
+
project_id: z.ZodString;
|
|
49
|
+
conversation_id: z.ZodString;
|
|
50
|
+
task_type: z.ZodEnum<["research", "ingest", "execute"]>;
|
|
51
|
+
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
52
|
+
}, "strip", z.ZodTypeAny, {
|
|
53
|
+
project_id: string;
|
|
54
|
+
conversation_id: string;
|
|
55
|
+
task_type: "research" | "ingest" | "execute";
|
|
56
|
+
params?: Record<string, unknown> | undefined;
|
|
57
|
+
}, {
|
|
58
|
+
project_id: string;
|
|
59
|
+
conversation_id: string;
|
|
60
|
+
task_type: "research" | "ingest" | "execute";
|
|
61
|
+
params?: Record<string, unknown> | undefined;
|
|
62
|
+
}>;
|
|
63
|
+
list_tasks: z.ZodObject<{
|
|
64
|
+
status: z.ZodOptional<z.ZodEnum<["pending", "in_progress", "completed", "failed"]>>;
|
|
65
|
+
task_type: z.ZodOptional<z.ZodEnum<["research", "ingest", "execute"]>>;
|
|
66
|
+
project_id: z.ZodOptional<z.ZodString>;
|
|
67
|
+
conversation_id: z.ZodOptional<z.ZodString>;
|
|
68
|
+
}, "strip", z.ZodTypeAny, {
|
|
69
|
+
status?: "pending" | "in_progress" | "completed" | "failed" | undefined;
|
|
70
|
+
project_id?: string | undefined;
|
|
71
|
+
conversation_id?: string | undefined;
|
|
72
|
+
task_type?: "research" | "ingest" | "execute" | undefined;
|
|
73
|
+
}, {
|
|
74
|
+
status?: "pending" | "in_progress" | "completed" | "failed" | undefined;
|
|
75
|
+
project_id?: string | undefined;
|
|
76
|
+
conversation_id?: string | undefined;
|
|
77
|
+
task_type?: "research" | "ingest" | "execute" | undefined;
|
|
78
|
+
}>;
|
|
79
|
+
get_task: z.ZodObject<{
|
|
80
|
+
id: z.ZodString;
|
|
81
|
+
}, "strip", z.ZodTypeAny, {
|
|
82
|
+
id: string;
|
|
83
|
+
}, {
|
|
84
|
+
id: string;
|
|
85
|
+
}>;
|
|
86
|
+
patch_task: z.ZodObject<{
|
|
87
|
+
id: z.ZodString;
|
|
88
|
+
action: z.ZodEnum<["claim", "complete", "fail", "consume"]>;
|
|
89
|
+
conversation_id: z.ZodOptional<z.ZodString>;
|
|
90
|
+
results: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
91
|
+
error: z.ZodOptional<z.ZodString>;
|
|
92
|
+
}, "strip", z.ZodTypeAny, {
|
|
93
|
+
id: string;
|
|
94
|
+
action: "claim" | "complete" | "fail" | "consume";
|
|
95
|
+
error?: string | undefined;
|
|
96
|
+
conversation_id?: string | undefined;
|
|
97
|
+
results?: Record<string, unknown> | undefined;
|
|
98
|
+
}, {
|
|
99
|
+
id: string;
|
|
100
|
+
action: "claim" | "complete" | "fail" | "consume";
|
|
101
|
+
error?: string | undefined;
|
|
102
|
+
conversation_id?: string | undefined;
|
|
103
|
+
results?: Record<string, unknown> | undefined;
|
|
104
|
+
}>;
|
|
105
|
+
list_creations: z.ZodObject<{
|
|
106
|
+
project_id: z.ZodString;
|
|
107
|
+
}, "strip", z.ZodTypeAny, {
|
|
108
|
+
project_id: string;
|
|
109
|
+
}, {
|
|
110
|
+
project_id: string;
|
|
111
|
+
}>;
|
|
112
|
+
get_creation: z.ZodObject<{
|
|
113
|
+
id: z.ZodString;
|
|
114
|
+
}, "strip", z.ZodTypeAny, {
|
|
115
|
+
id: string;
|
|
116
|
+
}, {
|
|
117
|
+
id: string;
|
|
118
|
+
}>;
|
|
119
|
+
create_creation: z.ZodObject<{
|
|
120
|
+
project_id: z.ZodString;
|
|
121
|
+
title: z.ZodString;
|
|
122
|
+
content: z.ZodString;
|
|
123
|
+
status: z.ZodDefault<z.ZodEnum<["draft", "saved", "finalized"]>>;
|
|
124
|
+
}, "strip", z.ZodTypeAny, {
|
|
125
|
+
status: "draft" | "saved" | "finalized";
|
|
126
|
+
project_id: string;
|
|
127
|
+
title: string;
|
|
128
|
+
content: string;
|
|
129
|
+
}, {
|
|
130
|
+
project_id: string;
|
|
131
|
+
title: string;
|
|
132
|
+
content: string;
|
|
133
|
+
status?: "draft" | "saved" | "finalized" | undefined;
|
|
134
|
+
}>;
|
|
135
|
+
update_creation: z.ZodObject<{
|
|
136
|
+
id: z.ZodString;
|
|
137
|
+
title: z.ZodOptional<z.ZodString>;
|
|
138
|
+
content: z.ZodOptional<z.ZodString>;
|
|
139
|
+
status: z.ZodOptional<z.ZodEnum<["draft", "saved", "finalized"]>>;
|
|
140
|
+
}, "strip", z.ZodTypeAny, {
|
|
141
|
+
id: string;
|
|
142
|
+
status?: "draft" | "saved" | "finalized" | undefined;
|
|
143
|
+
title?: string | undefined;
|
|
144
|
+
content?: string | undefined;
|
|
145
|
+
}, {
|
|
146
|
+
id: string;
|
|
147
|
+
status?: "draft" | "saved" | "finalized" | undefined;
|
|
148
|
+
title?: string | undefined;
|
|
149
|
+
content?: string | undefined;
|
|
150
|
+
}>;
|
|
151
|
+
list_references: z.ZodObject<{
|
|
152
|
+
project_id: z.ZodString;
|
|
153
|
+
}, "strip", z.ZodTypeAny, {
|
|
154
|
+
project_id: string;
|
|
155
|
+
}, {
|
|
156
|
+
project_id: string;
|
|
157
|
+
}>;
|
|
158
|
+
create_reference: z.ZodObject<{
|
|
159
|
+
project_id: z.ZodString;
|
|
160
|
+
kind: z.ZodDefault<z.ZodEnum<["url", "youtube", "text", "voice", "document"]>>;
|
|
161
|
+
title: z.ZodOptional<z.ZodString>;
|
|
162
|
+
content: z.ZodOptional<z.ZodString>;
|
|
163
|
+
url: z.ZodOptional<z.ZodString>;
|
|
164
|
+
filename: z.ZodOptional<z.ZodString>;
|
|
165
|
+
}, "strip", z.ZodTypeAny, {
|
|
166
|
+
kind: "text" | "url" | "youtube" | "voice" | "document";
|
|
167
|
+
project_id: string;
|
|
168
|
+
title?: string | undefined;
|
|
169
|
+
content?: string | undefined;
|
|
170
|
+
url?: string | undefined;
|
|
171
|
+
filename?: string | undefined;
|
|
172
|
+
}, {
|
|
173
|
+
project_id: string;
|
|
174
|
+
kind?: "text" | "url" | "youtube" | "voice" | "document" | undefined;
|
|
175
|
+
title?: string | undefined;
|
|
176
|
+
content?: string | undefined;
|
|
177
|
+
url?: string | undefined;
|
|
178
|
+
filename?: string | undefined;
|
|
179
|
+
}>;
|
|
180
|
+
};
|
|
181
|
+
export declare function createServer(authCtx?: ToolAuthContext): McpServer;
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { MISSING_KEY_MESSAGE, resolveAuth } from "./auth.js";
|
|
4
|
+
import { ApiError, OriginMindClient } from "./client.js";
|
|
5
|
+
function textResult(payload) {
|
|
6
|
+
const text = typeof payload === "string" ? payload : JSON.stringify(payload, null, 2);
|
|
7
|
+
return { content: [{ type: "text", text }] };
|
|
8
|
+
}
|
|
9
|
+
function errorResult(message) {
|
|
10
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
11
|
+
}
|
|
12
|
+
function clientFor(ctx) {
|
|
13
|
+
const auth = resolveAuth({ authorizationHeader: ctx.getAuthorizationHeader?.() ?? null });
|
|
14
|
+
if ("error" in auth)
|
|
15
|
+
return { error: MISSING_KEY_MESSAGE };
|
|
16
|
+
return new OriginMindClient(auth);
|
|
17
|
+
}
|
|
18
|
+
async function runTool(ctx, fn) {
|
|
19
|
+
const client = clientFor(ctx);
|
|
20
|
+
if ("error" in client)
|
|
21
|
+
return errorResult(client.error);
|
|
22
|
+
try {
|
|
23
|
+
const data = await fn(client);
|
|
24
|
+
return textResult(data);
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
if (err instanceof ApiError) {
|
|
28
|
+
return errorResult(`${err.message}\n${err.body}`);
|
|
29
|
+
}
|
|
30
|
+
return errorResult(err instanceof Error ? err.message : String(err));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** Zod shapes exported for unit tests. */
|
|
34
|
+
export const toolSchemas = {
|
|
35
|
+
list_projects: z.object({}),
|
|
36
|
+
search_creative_dna: z.object({
|
|
37
|
+
project_id: z.string().min(1),
|
|
38
|
+
query: z.string().min(1),
|
|
39
|
+
vectorK: z.number().int().min(1).max(20).optional(),
|
|
40
|
+
neighborLimit: z.number().int().min(1).max(5).optional(),
|
|
41
|
+
}),
|
|
42
|
+
list_conversations: z.object({
|
|
43
|
+
project_id: z.string().min(1),
|
|
44
|
+
}),
|
|
45
|
+
list_messages: z.object({
|
|
46
|
+
conversation_id: z.string().uuid(),
|
|
47
|
+
}),
|
|
48
|
+
get_transcript: z.object({
|
|
49
|
+
conversation_id: z.string().uuid(),
|
|
50
|
+
}),
|
|
51
|
+
create_task: z.object({
|
|
52
|
+
project_id: z.string().min(1),
|
|
53
|
+
conversation_id: z.string().uuid(),
|
|
54
|
+
task_type: z.enum(["research", "ingest", "execute"]),
|
|
55
|
+
params: z.record(z.unknown()).optional(),
|
|
56
|
+
}),
|
|
57
|
+
list_tasks: z.object({
|
|
58
|
+
status: z.enum(["pending", "in_progress", "completed", "failed"]).optional(),
|
|
59
|
+
task_type: z.enum(["research", "ingest", "execute"]).optional(),
|
|
60
|
+
project_id: z.string().optional(),
|
|
61
|
+
conversation_id: z.string().uuid().optional(),
|
|
62
|
+
}),
|
|
63
|
+
get_task: z.object({
|
|
64
|
+
id: z.string().uuid(),
|
|
65
|
+
}),
|
|
66
|
+
patch_task: z.object({
|
|
67
|
+
id: z.string().uuid(),
|
|
68
|
+
action: z.enum(["claim", "complete", "fail", "consume"]),
|
|
69
|
+
conversation_id: z.string().uuid().optional(),
|
|
70
|
+
results: z.record(z.unknown()).optional(),
|
|
71
|
+
error: z.string().optional(),
|
|
72
|
+
}),
|
|
73
|
+
list_creations: z.object({
|
|
74
|
+
project_id: z.string().min(1),
|
|
75
|
+
}),
|
|
76
|
+
get_creation: z.object({
|
|
77
|
+
id: z.string().uuid(),
|
|
78
|
+
}),
|
|
79
|
+
create_creation: z.object({
|
|
80
|
+
project_id: z.string().min(1),
|
|
81
|
+
title: z.string().min(1),
|
|
82
|
+
content: z.string().min(1),
|
|
83
|
+
status: z.enum(["draft", "saved", "finalized"]).default("saved"),
|
|
84
|
+
}),
|
|
85
|
+
update_creation: z.object({
|
|
86
|
+
id: z.string().uuid(),
|
|
87
|
+
title: z.string().optional(),
|
|
88
|
+
content: z.string().optional(),
|
|
89
|
+
status: z.enum(["draft", "saved", "finalized"]).optional(),
|
|
90
|
+
}),
|
|
91
|
+
list_references: z.object({
|
|
92
|
+
project_id: z.string().min(1),
|
|
93
|
+
}),
|
|
94
|
+
create_reference: z.object({
|
|
95
|
+
project_id: z.string().min(1),
|
|
96
|
+
kind: z.enum(["url", "youtube", "text", "voice", "document"]).default("document"),
|
|
97
|
+
title: z.string().min(1).optional(),
|
|
98
|
+
content: z.string().optional(),
|
|
99
|
+
url: z.string().url().optional(),
|
|
100
|
+
filename: z.string().optional(),
|
|
101
|
+
}),
|
|
102
|
+
};
|
|
103
|
+
export function createServer(authCtx = {}) {
|
|
104
|
+
const server = new McpServer({
|
|
105
|
+
name: "originmind",
|
|
106
|
+
version: "0.1.0",
|
|
107
|
+
});
|
|
108
|
+
server.tool("list_projects", "List OriginMind projects for the authenticated user (GET /api/v1/projects).", toolSchemas.list_projects.shape, async () => runTool(authCtx, (c) => c.get("/api/v1/projects")));
|
|
109
|
+
server.tool("search_creative_dna", "Search Creative DNA for a project (POST /api/v1/projects/{id}/query).", toolSchemas.search_creative_dna.shape, async (args) => runTool(authCtx, (c) => c.post(`/api/v1/projects/${encodeURIComponent(args.project_id)}/query`, {
|
|
110
|
+
query: args.query,
|
|
111
|
+
...(args.vectorK !== undefined ? { vectorK: args.vectorK } : {}),
|
|
112
|
+
...(args.neighborLimit !== undefined ? { neighborLimit: args.neighborLimit } : {}),
|
|
113
|
+
})));
|
|
114
|
+
server.tool("list_conversations", "List conversations for a project (GET /api/v1/projects/{id}/conversations).", toolSchemas.list_conversations.shape, async (args) => runTool(authCtx, (c) => c.get(`/api/v1/projects/${encodeURIComponent(args.project_id)}/conversations`)));
|
|
115
|
+
server.tool("list_messages", "List messages for a conversation (GET /api/v1/messages/{conversationId}).", toolSchemas.list_messages.shape, async (args) => runTool(authCtx, (c) => c.get(`/api/v1/messages/${encodeURIComponent(args.conversation_id)}`)));
|
|
116
|
+
server.tool("get_transcript", "Get plain-text transcript for a conversation (GET /api/v1/conversations/{id}/transcript).", toolSchemas.get_transcript.shape, async (args) => runTool(authCtx, (c) => c.get(`/api/v1/conversations/${encodeURIComponent(args.conversation_id)}/transcript`)));
|
|
117
|
+
server.tool("create_task", "Create a research/execute/ingest task (POST /api/v1/tasks).", toolSchemas.create_task.shape, async (args) => runTool(authCtx, (c) => c.post("/api/v1/tasks", {
|
|
118
|
+
project_id: args.project_id,
|
|
119
|
+
conversation_id: args.conversation_id,
|
|
120
|
+
task_type: args.task_type,
|
|
121
|
+
...(args.params !== undefined ? { params: args.params } : {}),
|
|
122
|
+
})));
|
|
123
|
+
server.tool("list_tasks", "List tasks with optional filters (GET /api/v1/tasks).", toolSchemas.list_tasks.shape, async (args) => runTool(authCtx, (c) => c.get("/api/v1/tasks", {
|
|
124
|
+
status: args.status,
|
|
125
|
+
task_type: args.task_type,
|
|
126
|
+
project_id: args.project_id,
|
|
127
|
+
conversation_id: args.conversation_id,
|
|
128
|
+
})));
|
|
129
|
+
server.tool("get_task", "Get a task by id (GET /api/v1/tasks/{id}).", toolSchemas.get_task.shape, async (args) => runTool(authCtx, (c) => c.get(`/api/v1/tasks/${encodeURIComponent(args.id)}`)));
|
|
130
|
+
server.tool("patch_task", "Claim, complete, fail, or consume a task (PATCH /api/v1/tasks/{id}).", toolSchemas.patch_task.shape, async (args) => runTool(authCtx, (c) => c.patch(`/api/v1/tasks/${encodeURIComponent(args.id)}`, {
|
|
131
|
+
action: args.action,
|
|
132
|
+
...(args.conversation_id !== undefined ? { conversation_id: args.conversation_id } : {}),
|
|
133
|
+
...(args.results !== undefined ? { results: args.results } : {}),
|
|
134
|
+
...(args.error !== undefined ? { error: args.error } : {}),
|
|
135
|
+
})));
|
|
136
|
+
server.tool("list_creations", "List creations for a project (GET /api/v1/creations?project_id=...).", toolSchemas.list_creations.shape, async (args) => runTool(authCtx, (c) => c.get("/api/v1/creations", { project_id: args.project_id })));
|
|
137
|
+
server.tool("get_creation", "Get a creation by id (GET /api/v1/creations/{id}).", toolSchemas.get_creation.shape, async (args) => runTool(authCtx, (c) => c.get(`/api/v1/creations/${encodeURIComponent(args.id)}`)));
|
|
138
|
+
server.tool("create_creation", "Create a saved creative document (POST /api/v1/creations). Not for source code.", toolSchemas.create_creation.shape, async (args) => runTool(authCtx, (c) => c.post("/api/v1/creations", {
|
|
139
|
+
project_id: args.project_id,
|
|
140
|
+
title: args.title,
|
|
141
|
+
content: args.content,
|
|
142
|
+
status: args.status ?? "saved",
|
|
143
|
+
})));
|
|
144
|
+
server.tool("update_creation", "Update a creation (PATCH /api/v1/creations/{id}). Do not PATCH finalized rows.", toolSchemas.update_creation.shape, async (args) => runTool(authCtx, (c) => c.patch(`/api/v1/creations/${encodeURIComponent(args.id)}`, {
|
|
145
|
+
...(args.title !== undefined ? { title: args.title } : {}),
|
|
146
|
+
...(args.content !== undefined ? { content: args.content } : {}),
|
|
147
|
+
...(args.status !== undefined ? { status: args.status } : {}),
|
|
148
|
+
})));
|
|
149
|
+
server.tool("list_references", "List references for a project (GET /api/v1/references?project_id=...).", toolSchemas.list_references.shape, async (args) => runTool(authCtx, (c) => c.get("/api/v1/references", { project_id: args.project_id })));
|
|
150
|
+
server.tool("create_reference", "Create a reference on the shelf (POST /api/v1/references). kind=document uploads markdown multipart; url/youtube/text/voice use JSON. Notion is not exposed via MCP.", toolSchemas.create_reference.shape, async (args) => runTool(authCtx, async (c) => {
|
|
151
|
+
const kind = args.kind ?? "document";
|
|
152
|
+
if (kind === "document") {
|
|
153
|
+
if (!args.content?.trim()) {
|
|
154
|
+
throw new Error("content is required for kind=document (markdown body).");
|
|
155
|
+
}
|
|
156
|
+
const name = args.filename?.trim() || `${(args.title || "note").replace(/[^\w.-]+/g, "-")}.md`;
|
|
157
|
+
return c.postMultipart("/api/v1/references", {
|
|
158
|
+
fields: { project_id: args.project_id },
|
|
159
|
+
file: {
|
|
160
|
+
fieldName: "file",
|
|
161
|
+
filename: name.endsWith(".md") ? name : `${name}.md`,
|
|
162
|
+
contentType: "text/markdown",
|
|
163
|
+
body: args.content,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
return c.post("/api/v1/references", {
|
|
168
|
+
project_id: args.project_id,
|
|
169
|
+
kind,
|
|
170
|
+
...(args.url !== undefined ? { url: args.url } : {}),
|
|
171
|
+
...(args.content !== undefined ? { content: args.content } : {}),
|
|
172
|
+
...(args.title !== undefined ? { link_title: args.title } : {}),
|
|
173
|
+
});
|
|
174
|
+
}));
|
|
175
|
+
return server;
|
|
176
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gloriginmind/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OriginMind MCP server — dual-auth skill routes via Bootstrap OAuth API key",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"originmind-mcp": "dist/stdio.js",
|
|
8
|
+
"originmind-mcp-http": "dist/http.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "./dist/stdio.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"start": "node dist/stdio.js",
|
|
18
|
+
"start:http": "node dist/http.js",
|
|
19
|
+
"test": "tsx --test src/**/*.test.ts"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git+https://github.com/OriginMind-ai/OriginMindAI-skill.git",
|
|
30
|
+
"directory": "skills/mcp"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@modelcontextprotocol/sdk": "^1.24.0",
|
|
34
|
+
"zod": "^3.24.1"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.10.5",
|
|
38
|
+
"tsx": "^4.19.2",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
40
|
+
}
|
|
41
|
+
}
|