scc-mcp 0.1.0 → 0.2.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/dispatch.js +98 -1
- package/dist/index.js +1 -1
- package/package.json +3 -2
package/dist/dispatch.js
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
1
|
+
// Turns a tool call into an HTTP request, driven entirely by the `exec` spec
|
|
2
|
+
// the server sent in the manifest. The package contains no endpoint knowledge
|
|
3
|
+
// of its own — this is a generic manifest→REST bridge.
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { basename, extname } from "node:path";
|
|
7
|
+
// Extension → MIME for local-file uploads. Mirrors the API's ALLOWED_MIME so the
|
|
8
|
+
// inferred type is one the upload endpoint accepts; unknown extensions fall back
|
|
9
|
+
// to an explicit `mime` arg (and the server rejects anything it can't handle).
|
|
10
|
+
const EXT_MIME = {
|
|
11
|
+
".jpg": "image/jpeg",
|
|
12
|
+
".jpeg": "image/jpeg",
|
|
13
|
+
".png": "image/png",
|
|
14
|
+
".webp": "image/webp",
|
|
15
|
+
".gif": "image/gif",
|
|
16
|
+
".svg": "image/svg+xml",
|
|
17
|
+
".pdf": "application/pdf",
|
|
18
|
+
".doc": "application/msword",
|
|
19
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
20
|
+
".xls": "application/vnd.ms-excel",
|
|
21
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
22
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
23
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
24
|
+
".txt": "text/plain",
|
|
25
|
+
".mp3": "audio/mpeg",
|
|
26
|
+
".wav": "audio/wav",
|
|
27
|
+
".ogg": "audio/ogg",
|
|
28
|
+
".m4a": "audio/mp4",
|
|
29
|
+
".aac": "audio/aac",
|
|
30
|
+
};
|
|
31
|
+
/** Expand a leading ~ to the user's home directory. */
|
|
32
|
+
function expandHome(p) {
|
|
33
|
+
if (p === "~")
|
|
34
|
+
return homedir();
|
|
35
|
+
if (p.startsWith("~/") || p.startsWith("~\\"))
|
|
36
|
+
return homedir() + p.slice(1);
|
|
37
|
+
return p;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* For a readFile exec: read the file at args[pathArg] off the local disk,
|
|
41
|
+
* base64-encode it, and return a new args object with { data, filename, mime }
|
|
42
|
+
* filled in (and the path arg removed). Throws a clear Error the caller turns
|
|
43
|
+
* into a tool-call error if the file can't be read or its type can't be inferred.
|
|
44
|
+
*/
|
|
45
|
+
function applyReadFile(exec, args) {
|
|
46
|
+
const spec = exec.readFile;
|
|
47
|
+
if (!spec)
|
|
48
|
+
return args;
|
|
49
|
+
const { pathArg, dataArg = "data", filenameArg = "filename", mimeArg = "mime" } = spec;
|
|
50
|
+
const raw = args[pathArg];
|
|
51
|
+
if (typeof raw !== "string" || !raw.trim()) {
|
|
52
|
+
throw new Error(`A file path (${pathArg}) is required.`);
|
|
53
|
+
}
|
|
54
|
+
const filePath = expandHome(raw.trim());
|
|
55
|
+
let bytes;
|
|
56
|
+
try {
|
|
57
|
+
bytes = readFileSync(filePath);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
61
|
+
throw new Error(`Could not read the file at ${filePath} — ${reason}`);
|
|
62
|
+
}
|
|
63
|
+
if (bytes.byteLength === 0)
|
|
64
|
+
throw new Error(`The file at ${filePath} is empty.`);
|
|
65
|
+
const out = { ...args };
|
|
66
|
+
delete out[pathArg];
|
|
67
|
+
out[dataArg] = bytes.toString("base64");
|
|
68
|
+
if (out[filenameArg] === undefined || out[filenameArg] === "") {
|
|
69
|
+
out[filenameArg] = basename(filePath);
|
|
70
|
+
}
|
|
71
|
+
if (out[mimeArg] === undefined || out[mimeArg] === "") {
|
|
72
|
+
const mime = EXT_MIME[extname(filePath).toLowerCase()];
|
|
73
|
+
if (!mime) {
|
|
74
|
+
throw new Error(`Could not determine the file type of ${basename(filePath)} from its extension; pass an explicit "${mimeArg}".`);
|
|
75
|
+
}
|
|
76
|
+
out[mimeArg] = mime;
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
1
80
|
/** Resolve {placeholders} in the path and return the args not consumed by it. */
|
|
2
81
|
function fillPath(template, args) {
|
|
3
82
|
const used = new Set();
|
|
@@ -55,7 +134,25 @@ function buildRequest(exec, args) {
|
|
|
55
134
|
return req;
|
|
56
135
|
}
|
|
57
136
|
export async function execute(client, exec, args) {
|
|
58
|
-
|
|
137
|
+
let effectiveArgs = args;
|
|
138
|
+
if (exec.readFile) {
|
|
139
|
+
try {
|
|
140
|
+
// Read + base64 the local file HERE (in the MCP), so the bytes go straight
|
|
141
|
+
// to the API and never pass through the model's context.
|
|
142
|
+
effectiveArgs = applyReadFile(exec, args);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
// Surface a read/inference failure as a normal failed result so toContent
|
|
146
|
+
// renders it as a tool error instead of crashing the request handler.
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
status: 0,
|
|
150
|
+
text: err instanceof Error ? err.message : String(err),
|
|
151
|
+
contentType: "text/plain",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const { method, path, query, body } = buildRequest(exec, effectiveArgs);
|
|
59
156
|
return client.request(method, path, { query, body });
|
|
60
157
|
}
|
|
61
158
|
/** Format an API result as MCP tool-call content. */
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ if (process.argv[2] === "install") {
|
|
|
15
15
|
});
|
|
16
16
|
process.exit(0);
|
|
17
17
|
}
|
|
18
|
-
const VERSION = "0.
|
|
18
|
+
const VERSION = "0.2.0";
|
|
19
19
|
const API_URL = process.env.SCC_API_URL ?? "http://localhost:3200";
|
|
20
20
|
const API_KEY = process.env.SCC_API_KEY ?? "";
|
|
21
21
|
if (!API_KEY) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scc-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Key-based MCP server exposing the Scarborough Community Choir API so an AI can query data and act like an assistant.",
|
|
6
6
|
"repository": {
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"start": "node dist/index.js",
|
|
22
22
|
"install:claude": "node dist/index.js install",
|
|
23
23
|
"build": "tsc",
|
|
24
|
-
"typecheck": "tsc --noEmit"
|
|
24
|
+
"typecheck": "tsc --noEmit",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
25
26
|
},
|
|
26
27
|
"dependencies": {
|
|
27
28
|
"@modelcontextprotocol/sdk": "^1.29.0",
|