@manudota/artist-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/dist/client.js +46 -0
- package/dist/config.js +45 -0
- package/dist/index.js +27 -0
- package/dist/init.js +55 -0
- package/dist/server.js +40 -0
- package/package.json +30 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only network surface this package has.
|
|
3
|
+
*
|
|
4
|
+
* Everything goes through one POST to the edge function, which holds the
|
|
5
|
+
* Microsoft credentials. No Graph call is ever made from this machine, and the
|
|
6
|
+
* connection key is the only secret that lives here.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_ENDPOINT = "https://zxiemadwrkcoovvpscfb.supabase.co/functions/v1/graph";
|
|
9
|
+
/** Overridable for testing; the baked-in default is what shipped copies use. */
|
|
10
|
+
export function endpoint() {
|
|
11
|
+
return process.env.ARTIST_MCP_ENDPOINT ?? DEFAULT_ENDPOINT;
|
|
12
|
+
}
|
|
13
|
+
export class GraphError extends Error {
|
|
14
|
+
reconnectNeeded;
|
|
15
|
+
constructor(message, reconnectNeeded) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.reconnectNeeded = reconnectNeeded;
|
|
18
|
+
this.name = "GraphError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function call(op, key, params = {}) {
|
|
22
|
+
let res;
|
|
23
|
+
try {
|
|
24
|
+
res = await fetch(endpoint(), {
|
|
25
|
+
method: "POST",
|
|
26
|
+
headers: {
|
|
27
|
+
"content-type": "application/json",
|
|
28
|
+
authorization: `Bearer ${key}`,
|
|
29
|
+
},
|
|
30
|
+
body: JSON.stringify({ op, ...params }),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (cause) {
|
|
34
|
+
throw new GraphError(`Could not reach the notes service: ${cause}`, false);
|
|
35
|
+
}
|
|
36
|
+
if (res.status === 401 || res.status === 403) {
|
|
37
|
+
throw new GraphError("Reconnect needed — this connection key is no longer valid. " +
|
|
38
|
+
"Sign in to the web app and generate a new one.", true);
|
|
39
|
+
}
|
|
40
|
+
const body = (await res.json().catch(() => ({})));
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
const detail = typeof body.error === "string" ? body.error : `HTTP ${res.status}`;
|
|
43
|
+
throw new GraphError(detail, body.reconnect_needed === true);
|
|
44
|
+
}
|
|
45
|
+
return body;
|
|
46
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** Locating and editing the Claude Desktop config, without trampling it. */
|
|
2
|
+
import { homedir, platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname } from "node:path";
|
|
6
|
+
export const ENTRY_NAME = "artist-notes";
|
|
7
|
+
export function configPath() {
|
|
8
|
+
const home = homedir();
|
|
9
|
+
switch (platform()) {
|
|
10
|
+
case "darwin":
|
|
11
|
+
return join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
12
|
+
case "win32":
|
|
13
|
+
return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
14
|
+
default:
|
|
15
|
+
// Claude Desktop ships on macOS and Windows only; this keeps the error
|
|
16
|
+
// legible rather than writing a config nothing will ever read.
|
|
17
|
+
throw new Error(`Claude Desktop has no config location on ${platform()}. ` +
|
|
18
|
+
"Install on macOS or Windows.");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Missing file is not an error — it's the first-install case. */
|
|
22
|
+
export async function readConfig(path) {
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = await readFile(path, "utf8");
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
if (err.code === "ENOENT")
|
|
29
|
+
return {};
|
|
30
|
+
throw err;
|
|
31
|
+
}
|
|
32
|
+
if (raw.trim() === "")
|
|
33
|
+
return {};
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(raw);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
throw new Error(`${path} is not valid JSON. Fix or move it, then run init again — ` +
|
|
39
|
+
"refusing to overwrite a file that may hold your other MCP servers.");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function writeConfig(path, config) {
|
|
43
|
+
await mkdir(dirname(path), { recursive: true });
|
|
44
|
+
await writeFile(path, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
45
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runInit, runUninstall } from "./init.js";
|
|
3
|
+
import { runServer } from "./server.js";
|
|
4
|
+
const mode = process.argv[2];
|
|
5
|
+
try {
|
|
6
|
+
switch (mode) {
|
|
7
|
+
case undefined:
|
|
8
|
+
await runServer();
|
|
9
|
+
break;
|
|
10
|
+
case "init":
|
|
11
|
+
await runInit();
|
|
12
|
+
break;
|
|
13
|
+
case "uninstall":
|
|
14
|
+
await runUninstall();
|
|
15
|
+
break;
|
|
16
|
+
default:
|
|
17
|
+
console.error(`Unknown command "${mode}".\n\n` +
|
|
18
|
+
" artist-mcp run the MCP server over stdio\n" +
|
|
19
|
+
" artist-mcp init connect this machine to your notes\n" +
|
|
20
|
+
" artist-mcp uninstall remove the Claude Desktop entry\n");
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
package/dist/init.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { stdin, stdout } from "node:process";
|
|
3
|
+
import { call, GraphError } from "./client.js";
|
|
4
|
+
import { configPath, ENTRY_NAME, readConfig, writeConfig } from "./config.js";
|
|
5
|
+
const PACKAGE = "@manudota/artist-mcp";
|
|
6
|
+
export async function runInit() {
|
|
7
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
8
|
+
let key;
|
|
9
|
+
try {
|
|
10
|
+
key = (await rl.question("Paste your connection key: ")).trim();
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
rl.close();
|
|
14
|
+
}
|
|
15
|
+
if (key === "") {
|
|
16
|
+
console.error("No key entered. Nothing was written.");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
// Verify before touching the config — a bad key should leave the machine
|
|
20
|
+
// exactly as it was.
|
|
21
|
+
try {
|
|
22
|
+
await call("verify", key);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
const message = err instanceof GraphError ? err.message : `Could not verify key: ${err}`;
|
|
26
|
+
console.error(`${message}\nNothing was written.`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const path = configPath();
|
|
30
|
+
const config = await readConfig(path);
|
|
31
|
+
const servers = (config.mcpServers ?? {});
|
|
32
|
+
servers[ENTRY_NAME] = {
|
|
33
|
+
command: "npx",
|
|
34
|
+
args: ["-y", PACKAGE],
|
|
35
|
+
env: { ARTIST_MCP_KEY: key },
|
|
36
|
+
};
|
|
37
|
+
config.mcpServers = servers;
|
|
38
|
+
await writeConfig(path, config);
|
|
39
|
+
console.log(`\nConnected. Wrote "${ENTRY_NAME}" to ${path}`);
|
|
40
|
+
console.log("Restart Claude Desktop — it doesn't reload its config on its own.");
|
|
41
|
+
}
|
|
42
|
+
export async function runUninstall() {
|
|
43
|
+
const path = configPath();
|
|
44
|
+
const config = await readConfig(path);
|
|
45
|
+
const servers = (config.mcpServers ?? {});
|
|
46
|
+
if (!(ENTRY_NAME in servers)) {
|
|
47
|
+
console.log(`No "${ENTRY_NAME}" entry in ${path}. Nothing to do.`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
delete servers[ENTRY_NAME];
|
|
51
|
+
config.mcpServers = servers;
|
|
52
|
+
await writeConfig(path, config);
|
|
53
|
+
console.log(`Removed "${ENTRY_NAME}" from ${path}`);
|
|
54
|
+
console.log("Restart Claude Desktop to apply.");
|
|
55
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { call, GraphError } from "./client.js";
|
|
5
|
+
export async function runServer() {
|
|
6
|
+
const key = process.env.ARTIST_MCP_KEY;
|
|
7
|
+
if (!key) {
|
|
8
|
+
// stderr, not stdout — stdout is the protocol channel.
|
|
9
|
+
console.error("ARTIST_MCP_KEY is not set. Run `npx @manudota/artist-mcp init` to configure.");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const server = new McpServer({ name: "artist-notes", version: "0.1.0" });
|
|
13
|
+
server.tool("list_notes", "List the user's OneNote pages, with title, section and last modified date.", {}, async () => {
|
|
14
|
+
try {
|
|
15
|
+
const { notes } = await call("list_notes", key);
|
|
16
|
+
if (notes.length === 0) {
|
|
17
|
+
return { content: [{ type: "text", text: "No notes found." }] };
|
|
18
|
+
}
|
|
19
|
+
const lines = notes.map((n) => `- ${n.title}${n.section ? ` (${n.section})` : ""} — modified ${n.last_modified ?? "unknown"}\n id: ${n.id}`);
|
|
20
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
return errorResult(err);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
server.tool("read_note", "Read the text content of one OneNote page. Takes the id from list_notes.", { note_id: z.string().describe("The id of the note, as returned by list_notes") }, async ({ note_id }) => {
|
|
27
|
+
try {
|
|
28
|
+
const { title, text } = await call("read_note", key, { note_id });
|
|
29
|
+
return { content: [{ type: "text", text: `# ${title}\n\n${text}` }] };
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
return errorResult(err);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
await server.connect(new StdioServerTransport());
|
|
36
|
+
}
|
|
37
|
+
function errorResult(err) {
|
|
38
|
+
const message = err instanceof GraphError ? err.message : `Unexpected error: ${err}`;
|
|
39
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
40
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@manudota/artist-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server that reads your OneNote notes, via a hosted connection.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"artist-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=20"
|
|
14
|
+
},
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.json && chmod +x dist/index.js",
|
|
20
|
+
"prepublishOnly": "pnpm run build"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
24
|
+
"zod": "^3.23.8"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^22.10.2",
|
|
28
|
+
"typescript": "^5.7.2"
|
|
29
|
+
}
|
|
30
|
+
}
|