@modootoday/pmcp 0.8.0 → 0.8.1
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 +12 -1
- package/SECURITY.md +5 -0
- package/dist/{chunk-M6LBYJDS.js → chunk-DTQAN33I.js} +2 -2
- package/dist/{chunk-ZDV75HZN.js → chunk-HB4VJ5VW.js} +48 -12
- package/dist/{chunk-LUBYOET7.js → chunk-K7BX33LH.js} +2 -2
- package/dist/{chunk-OPDVUHTK.js → chunk-MO3LLVVY.js} +2 -2
- package/dist/{chunk-WSTCBVXW.js → chunk-STBBSQ3R.js} +2 -2
- package/dist/cli.js +6 -6
- package/dist/http.d.ts +22 -1
- package/dist/http.js +142 -21
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -3
- package/dist/stdio.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -189,7 +189,18 @@ const skills = createSkillHttpHandler({
|
|
|
189
189
|
export default { fetch: (request: Request) => skills.fetch(request) };
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
The handler checks requests before they reach MCP:
|
|
193
|
+
|
|
194
|
+
| Option | Default | Effect |
|
|
195
|
+
| ----------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
196
|
+
| `allowedOrigins` | none | A request with an `Origin` other than the server's own gets 403. Listed origins are served with CORS headers, and their preflight is answered. Requests without `Origin` are not from a browser page and pass. |
|
|
197
|
+
| `allowedHosts` | any | When set, a `Host` outside the list gets 403 (DNS rebinding). |
|
|
198
|
+
| `maxRequestBytes` | 1 MiB | A larger body gets 413; a declared length is refused before reading. |
|
|
199
|
+
| `rateLimit` | off | `{ perMinute }` per caller id; past it, 429 with `retry-after`. |
|
|
200
|
+
|
|
201
|
+
An MCP config (`.mcp.json`) is never served as written: every `env` and `headers` value is
|
|
202
|
+
replaced with `<redacted>`, whichever entry reaches the file, and listed digests describe
|
|
203
|
+
the redacted bytes.
|
|
193
204
|
|
|
194
205
|
Pass `semantic: { cacheDir, localOnly: true }` to rank by meaning: the handler encodes
|
|
195
206
|
the catalog at start with `@huggingface/transformers` and the multilingual model, so an
|
package/SECURITY.md
CHANGED
|
@@ -20,6 +20,11 @@ Inspect the plan with `--dry-run`; non-interactive application requires `--yes`.
|
|
|
20
20
|
The CLI disables dependency install scripts for skill-package installation and
|
|
21
21
|
checks catalog integrity against registry metadata before applying a plan.
|
|
22
22
|
|
|
23
|
+
The server returns skill files as text or base64 and never runs them. An MCP config
|
|
24
|
+
(`.mcp.json`) is served with every `env` and `headers` value redacted. The HTTP handler
|
|
25
|
+
refuses browser origins it was not given, and caps request size; host names and a
|
|
26
|
+
per-caller rate limit are options. Authentication and call records belong to the host.
|
|
27
|
+
|
|
23
28
|
## Supported versions
|
|
24
29
|
|
|
25
30
|
Security fixes are released on the latest published version. Include the pmcp
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
frontmatterObject,
|
|
3
3
|
readCatalog,
|
|
4
4
|
specProblems
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-HB4VJ5VW.js";
|
|
6
6
|
|
|
7
7
|
// src/config.ts
|
|
8
8
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -1139,4 +1139,4 @@ export {
|
|
|
1139
1139
|
validateSkillDir,
|
|
1140
1140
|
validateCatalog
|
|
1141
1141
|
};
|
|
1142
|
-
//# sourceMappingURL=chunk-
|
|
1142
|
+
//# sourceMappingURL=chunk-DTQAN33I.js.map
|
|
@@ -624,16 +624,49 @@ function registerSkillPrompts(server, tools) {
|
|
|
624
624
|
// src/files/list.ts
|
|
625
625
|
import { createHash } from "crypto";
|
|
626
626
|
import { readFileSync as readFileSync3, readdirSync as readdirSync3, statSync } from "fs";
|
|
627
|
-
import { basename as
|
|
627
|
+
import { basename as basename4, join as join3, relative as relative2, sep } from "path";
|
|
628
|
+
|
|
629
|
+
// src/files/redact.ts
|
|
630
|
+
import { basename as basename3 } from "path";
|
|
631
|
+
var REDACTED = "<redacted>";
|
|
632
|
+
var SECRET_MAPS = /* @__PURE__ */ new Set(["env", "headers"]);
|
|
633
|
+
function redactValue(value) {
|
|
634
|
+
if (Array.isArray(value)) return value.map(redactValue);
|
|
635
|
+
if (value === null || typeof value !== "object") return value;
|
|
636
|
+
const out = {};
|
|
637
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
638
|
+
const isSecretMap = SECRET_MAPS.has(key.toLowerCase()) && inner !== null && typeof inner === "object" && !Array.isArray(inner);
|
|
639
|
+
out[key] = isSecretMap ? Object.fromEntries(Object.keys(inner).map((name) => [name, REDACTED])) : redactValue(inner);
|
|
640
|
+
}
|
|
641
|
+
return out;
|
|
642
|
+
}
|
|
643
|
+
function redactMcpConfig(text) {
|
|
644
|
+
let parsed;
|
|
645
|
+
try {
|
|
646
|
+
parsed = JSON.parse(text);
|
|
647
|
+
} catch {
|
|
648
|
+
return `${JSON.stringify({ redacted: REDACTED, reason: "not valid JSON" }, null, 2)}
|
|
649
|
+
`;
|
|
650
|
+
}
|
|
651
|
+
return `${JSON.stringify(redactValue(parsed), null, 2)}
|
|
652
|
+
`;
|
|
653
|
+
}
|
|
654
|
+
var isMcpConfig = (path) => basename3(path) === ".mcp.json";
|
|
655
|
+
function servedBytes(path, raw) {
|
|
656
|
+
if (!isMcpConfig(path)) return raw;
|
|
657
|
+
return Buffer.from(redactMcpConfig(raw.toString("utf8")), "utf8");
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// src/files/list.ts
|
|
628
661
|
var FILE_LIST_LIMIT = 200;
|
|
629
662
|
var sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
|
|
630
663
|
function describe(path, full) {
|
|
631
|
-
const bytes = readFileSync3(full);
|
|
664
|
+
const bytes = servedBytes(full, readFileSync3(full));
|
|
632
665
|
return { path, bytes: bytes.length, sha256: sha256(bytes) };
|
|
633
666
|
}
|
|
634
667
|
function listSkillFiles(root, limit = FILE_LIST_LIMIT) {
|
|
635
668
|
if (statSync(root).isFile()) {
|
|
636
|
-
return { files: [describe(
|
|
669
|
+
return { files: [describe(basename4(root), root)], truncated: false };
|
|
637
670
|
}
|
|
638
671
|
const files = [];
|
|
639
672
|
const stack = [root];
|
|
@@ -803,7 +836,7 @@ function readSkillFile(entry, wanted, budget = READ_BUDGET_BYTES) {
|
|
|
803
836
|
detail: `${size} bytes > ${budget}`
|
|
804
837
|
};
|
|
805
838
|
}
|
|
806
|
-
const bytes = readFileSync4(resolved.target);
|
|
839
|
+
const bytes = servedBytes(resolved.target, readFileSync4(resolved.target));
|
|
807
840
|
const { kind: kind2, mimeType } = classify(resolved.path, bytes);
|
|
808
841
|
return {
|
|
809
842
|
ok: true,
|
|
@@ -1130,7 +1163,9 @@ function createSkillTools(options) {
|
|
|
1130
1163
|
const own = ownFile(entry);
|
|
1131
1164
|
return {
|
|
1132
1165
|
name: entry.name,
|
|
1133
|
-
body: readBody(
|
|
1166
|
+
body: readBody(
|
|
1167
|
+
servedBytes(entry.path, readFileSync6(entry.path)).toString("utf8")
|
|
1168
|
+
),
|
|
1134
1169
|
files: bundle.files.filter((file) => file.path !== own),
|
|
1135
1170
|
filesTruncated: bundle.truncated
|
|
1136
1171
|
};
|
|
@@ -1150,7 +1185,7 @@ function createSkillTools(options) {
|
|
|
1150
1185
|
// src/extension/entry.ts
|
|
1151
1186
|
import { createHash as createHash2 } from "crypto";
|
|
1152
1187
|
import { readFileSync as readFileSync7, readdirSync as readdirSync4, statSync as statSync6 } from "fs";
|
|
1153
|
-
import { basename as
|
|
1188
|
+
import { basename as basename5, dirname as dirname5, join as join5, relative as relative4, sep as sep3 } from "path";
|
|
1154
1189
|
var MAX_RESOURCES = 512;
|
|
1155
1190
|
var MAX_SKILL_BYTES = 16 * 1024 * 1024;
|
|
1156
1191
|
var digests = /* @__PURE__ */ new Map();
|
|
@@ -1159,8 +1194,9 @@ function facts(path) {
|
|
|
1159
1194
|
const key = `${stat.mtimeMs}:${stat.size}`;
|
|
1160
1195
|
const cached = digests.get(path);
|
|
1161
1196
|
if (cached?.key === key) return cached;
|
|
1162
|
-
const
|
|
1163
|
-
const
|
|
1197
|
+
const bytes = servedBytes(path, readFileSync7(path));
|
|
1198
|
+
const digest = `sha256:${createHash2("sha256").update(bytes).digest("hex")}`;
|
|
1199
|
+
const fresh = { key, digest, size: bytes.length };
|
|
1164
1200
|
digests.set(path, fresh);
|
|
1165
1201
|
return fresh;
|
|
1166
1202
|
}
|
|
@@ -1180,11 +1216,11 @@ function filesUnder(root) {
|
|
|
1180
1216
|
return found.sort();
|
|
1181
1217
|
}
|
|
1182
1218
|
function extensionEntry(entry) {
|
|
1183
|
-
if (kindOf(entry) !== "skill" ||
|
|
1219
|
+
if (kindOf(entry) !== "skill" || basename5(entry.path) !== "SKILL.md")
|
|
1184
1220
|
return null;
|
|
1185
1221
|
const frontmatter = frontmatterObject(readFileSync7(entry.path, "utf8"));
|
|
1186
1222
|
const root = dirname5(entry.path);
|
|
1187
|
-
if (specProblems(frontmatter,
|
|
1223
|
+
if (specProblems(frontmatter, basename5(root)).length > 0) return null;
|
|
1188
1224
|
if (frontmatter?.["name"] !== entry.slug) return null;
|
|
1189
1225
|
const files = filesUnder(root);
|
|
1190
1226
|
if (files === null) return null;
|
|
@@ -1554,7 +1590,7 @@ function registerReadTool(server, tools) {
|
|
|
1554
1590
|
|
|
1555
1591
|
// src/mcp.ts
|
|
1556
1592
|
var SERVER_NAME = "pmcp";
|
|
1557
|
-
var SERVER_VERSION = "0.8.
|
|
1593
|
+
var SERVER_VERSION = "0.8.1";
|
|
1558
1594
|
function registerSkillTools(server, tools) {
|
|
1559
1595
|
registerCatalogTool(server, tools);
|
|
1560
1596
|
registerFindTool(server, tools);
|
|
@@ -1625,4 +1661,4 @@ export {
|
|
|
1625
1661
|
registerSkillTools,
|
|
1626
1662
|
createSkillServer
|
|
1627
1663
|
};
|
|
1628
|
-
//# sourceMappingURL=chunk-
|
|
1664
|
+
//# sourceMappingURL=chunk-HB4VJ5VW.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createSkillServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HB4VJ5VW.js";
|
|
4
4
|
|
|
5
5
|
// src/stdio.ts
|
|
6
6
|
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
@@ -38,4 +38,4 @@ export {
|
|
|
38
38
|
serve,
|
|
39
39
|
main
|
|
40
40
|
};
|
|
41
|
-
//# sourceMappingURL=chunk-
|
|
41
|
+
//# sourceMappingURL=chunk-K7BX33LH.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
normaliseIntent
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HB4VJ5VW.js";
|
|
4
4
|
|
|
5
5
|
// src/cache.ts
|
|
6
6
|
async function openDatabase(path) {
|
|
@@ -131,4 +131,4 @@ export {
|
|
|
131
131
|
DEFAULT_INTENT_CACHE_LIMIT,
|
|
132
132
|
openIntentCache
|
|
133
133
|
};
|
|
134
|
-
//# sourceMappingURL=chunk-
|
|
134
|
+
//# sourceMappingURL=chunk-MO3LLVVY.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
openDatabase
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MO3LLVVY.js";
|
|
4
4
|
|
|
5
5
|
// src/embed.ts
|
|
6
6
|
var DEFAULT_MODEL_ID = "Xenova/multilingual-e5-small";
|
|
@@ -157,4 +157,4 @@ export {
|
|
|
157
157
|
encodeEntries,
|
|
158
158
|
openEntryVectors
|
|
159
159
|
};
|
|
160
|
-
//# sourceMappingURL=chunk-
|
|
160
|
+
//# sourceMappingURL=chunk-STBBSQ3R.js.map
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
loadEmbedder,
|
|
4
4
|
openEntryVectors
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-STBBSQ3R.js";
|
|
6
6
|
import {
|
|
7
7
|
ConfigError,
|
|
8
8
|
ProjectError,
|
|
@@ -17,15 +17,15 @@ import {
|
|
|
17
17
|
splitFrontmatter,
|
|
18
18
|
validateCatalog,
|
|
19
19
|
validateSkillDir
|
|
20
|
-
} from "./chunk-
|
|
21
|
-
import "./chunk-
|
|
20
|
+
} from "./chunk-DTQAN33I.js";
|
|
21
|
+
import "./chunk-MO3LLVVY.js";
|
|
22
22
|
import {
|
|
23
23
|
serve
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-K7BX33LH.js";
|
|
25
25
|
import {
|
|
26
26
|
kindOf,
|
|
27
27
|
readCatalog
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-HB4VJ5VW.js";
|
|
29
29
|
|
|
30
30
|
// src/cli/command.ts
|
|
31
31
|
var ArgumentError = class extends Error {
|
|
@@ -2347,7 +2347,7 @@ function dispatch(argv, options = {}) {
|
|
|
2347
2347
|
}
|
|
2348
2348
|
return run(command, rest, { ui, env, cwd });
|
|
2349
2349
|
}
|
|
2350
|
-
var VERSION = "0.8.
|
|
2350
|
+
var VERSION = "0.8.1";
|
|
2351
2351
|
async function run(command, argv, context) {
|
|
2352
2352
|
try {
|
|
2353
2353
|
return await command.run({
|
package/dist/http.d.ts
CHANGED
|
@@ -37,6 +37,19 @@ interface LoadEmbedderOptions {
|
|
|
37
37
|
*/
|
|
38
38
|
declare function loadEmbedder(modelId?: string, options?: LoadEmbedderOptions): Promise<Embedder | null>;
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Checks a hosted skills server makes before a request reaches MCP: which browser origins
|
|
42
|
+
* and Host names may call it, how large a request may be, and how often one caller may call.
|
|
43
|
+
* Requests without an Origin header are not from a browser page and are not origin-checked.
|
|
44
|
+
*/
|
|
45
|
+
declare const DEFAULT_MAX_REQUEST_BYTES: number;
|
|
46
|
+
interface RateLimitOptions {
|
|
47
|
+
/** Requests one caller may make in a minute. */
|
|
48
|
+
readonly perMinute: number;
|
|
49
|
+
/** Injected by tests. */
|
|
50
|
+
readonly now?: () => number;
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
/**
|
|
41
54
|
* The catalog over streamable HTTP, for a hosted skills server.
|
|
42
55
|
*
|
|
@@ -75,6 +88,14 @@ interface SkillHttpOptions extends SkillServerOptions {
|
|
|
75
88
|
* here, so "private" unless the host knows every caller sees the same list.
|
|
76
89
|
*/
|
|
77
90
|
readonly cacheScope?: "public" | "private";
|
|
91
|
+
/** Browser origins, besides the server's own, whose pages may call; others get 403. */
|
|
92
|
+
readonly allowedOrigins?: readonly string[];
|
|
93
|
+
/** Host header values the server answers to; unset means any. */
|
|
94
|
+
readonly allowedHosts?: readonly string[];
|
|
95
|
+
/** Largest request body accepted; larger ones get 413. */
|
|
96
|
+
readonly maxRequestBytes?: number;
|
|
97
|
+
/** Per-caller request ceiling; unset means the host limits rate itself. */
|
|
98
|
+
readonly rateLimit?: RateLimitOptions;
|
|
78
99
|
}
|
|
79
100
|
declare const DEFAULT_TIERS: readonly string[];
|
|
80
101
|
declare function visibleTo(entries: readonly SkillEntry[], tiers: readonly string[]): SkillEntry[];
|
|
@@ -87,4 +108,4 @@ interface SkillHttpHandler {
|
|
|
87
108
|
}
|
|
88
109
|
declare function createSkillHttpHandler(options: SkillHttpOptions): SkillHttpHandler;
|
|
89
110
|
|
|
90
|
-
export { DEFAULT_MODEL_ID, DEFAULT_TIERS, Embedder, type SkillCaller, type SkillHttpHandler, type SkillHttpOptions, type SkillHttpSemanticOptions, createSkillHttpHandler, loadEmbedder, visibleTo };
|
|
111
|
+
export { DEFAULT_MAX_REQUEST_BYTES, DEFAULT_MODEL_ID, DEFAULT_TIERS, Embedder, type RateLimitOptions, type SkillCaller, type SkillHttpHandler, type SkillHttpOptions, type SkillHttpSemanticOptions, createSkillHttpHandler, loadEmbedder, visibleTo };
|
package/dist/http.js
CHANGED
|
@@ -2,17 +2,120 @@ import {
|
|
|
2
2
|
DEFAULT_MODEL_ID,
|
|
3
3
|
encodeEntries,
|
|
4
4
|
loadEmbedder
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-STBBSQ3R.js";
|
|
6
|
+
import "./chunk-MO3LLVVY.js";
|
|
7
7
|
import {
|
|
8
8
|
createSkillServer,
|
|
9
9
|
readCatalog
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HB4VJ5VW.js";
|
|
11
11
|
|
|
12
12
|
// src/http.ts
|
|
13
13
|
import {
|
|
14
14
|
createMcpHandler
|
|
15
15
|
} from "@modelcontextprotocol/server";
|
|
16
|
+
|
|
17
|
+
// src/http-guard.ts
|
|
18
|
+
var DEFAULT_MAX_REQUEST_BYTES = 1024 * 1024;
|
|
19
|
+
var RATE_WINDOW_MS = 6e4;
|
|
20
|
+
var CORS_ALLOW_HEADERS = "authorization, content-type, accept, mcp-session-id, mcp-protocol-version, last-event-id";
|
|
21
|
+
var json = (status, body, headers = {}) => new Response(JSON.stringify(body), {
|
|
22
|
+
status,
|
|
23
|
+
headers: { "content-type": "application/json", ...headers }
|
|
24
|
+
});
|
|
25
|
+
function checkOrigin(request, allowedOrigins = []) {
|
|
26
|
+
const origin = request.headers.get("origin");
|
|
27
|
+
if (origin === null) return { kind: "none" };
|
|
28
|
+
if (origin === new URL(request.url).origin) return { kind: "same" };
|
|
29
|
+
if (allowedOrigins.includes(origin)) return { kind: "allowed", origin };
|
|
30
|
+
return {
|
|
31
|
+
kind: "refused",
|
|
32
|
+
response: json(403, { error: "origin_not_allowed" })
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function checkHost(request, allowedHosts) {
|
|
36
|
+
if (!allowedHosts) return void 0;
|
|
37
|
+
const host = request.headers.get("host") ?? new URL(request.url).host;
|
|
38
|
+
return allowedHosts.includes(host) ? void 0 : json(403, { error: "host_not_allowed" });
|
|
39
|
+
}
|
|
40
|
+
function corsHeaders(origin) {
|
|
41
|
+
return {
|
|
42
|
+
"access-control-allow-origin": origin,
|
|
43
|
+
"access-control-expose-headers": "mcp-session-id, www-authenticate",
|
|
44
|
+
vary: "Origin"
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function preflight(origin) {
|
|
48
|
+
return new Response(null, {
|
|
49
|
+
status: 204,
|
|
50
|
+
headers: {
|
|
51
|
+
...corsHeaders(origin),
|
|
52
|
+
"access-control-allow-methods": "GET, POST, DELETE, OPTIONS",
|
|
53
|
+
"access-control-allow-headers": CORS_ALLOW_HEADERS,
|
|
54
|
+
"access-control-max-age": "600"
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function withHeaders(response, headers) {
|
|
59
|
+
const merged = new Headers(response.headers);
|
|
60
|
+
for (const [name, value] of Object.entries(headers)) merged.set(name, value);
|
|
61
|
+
return new Response(response.body, {
|
|
62
|
+
status: response.status,
|
|
63
|
+
statusText: response.statusText,
|
|
64
|
+
headers: merged
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
var tooLarge = (max) => json(413, { error: "request_too_large", limit: max });
|
|
68
|
+
async function capBody(request, max) {
|
|
69
|
+
const declared = Number(request.headers.get("content-length") ?? NaN);
|
|
70
|
+
if (Number.isFinite(declared) && declared > max) return tooLarge(max);
|
|
71
|
+
if (!request.body) return request;
|
|
72
|
+
const reader = request.body.getReader();
|
|
73
|
+
const chunks = [];
|
|
74
|
+
let total = 0;
|
|
75
|
+
for (; ; ) {
|
|
76
|
+
const { done, value } = await reader.read();
|
|
77
|
+
if (done) break;
|
|
78
|
+
total += value.byteLength;
|
|
79
|
+
if (total > max) {
|
|
80
|
+
await reader.cancel();
|
|
81
|
+
return tooLarge(max);
|
|
82
|
+
}
|
|
83
|
+
chunks.push(value);
|
|
84
|
+
}
|
|
85
|
+
const body = new Uint8Array(total);
|
|
86
|
+
let offset = 0;
|
|
87
|
+
for (const chunk of chunks) {
|
|
88
|
+
body.set(chunk, offset);
|
|
89
|
+
offset += chunk.byteLength;
|
|
90
|
+
}
|
|
91
|
+
return new Request(request.url, {
|
|
92
|
+
method: request.method,
|
|
93
|
+
headers: request.headers,
|
|
94
|
+
body
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function createRateLimiter(options) {
|
|
98
|
+
const now = options.now ?? Date.now;
|
|
99
|
+
const windows = /* @__PURE__ */ new Map();
|
|
100
|
+
return (callerId) => {
|
|
101
|
+
const t = now();
|
|
102
|
+
const window = windows.get(callerId);
|
|
103
|
+
if (!window || t - window.start >= RATE_WINDOW_MS) {
|
|
104
|
+
windows.set(callerId, { start: t, count: 1 });
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
window.count += 1;
|
|
108
|
+
if (window.count <= options.perMinute) return void 0;
|
|
109
|
+
const retry = Math.ceil((window.start + RATE_WINDOW_MS - t) / 1e3);
|
|
110
|
+
return json(
|
|
111
|
+
429,
|
|
112
|
+
{ error: "rate_limited" },
|
|
113
|
+
{ "retry-after": String(Math.max(retry, 1)) }
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// src/http.ts
|
|
16
119
|
var DEFAULT_TIERS = ["open", "free"];
|
|
17
120
|
function visibleTo(entries, tiers) {
|
|
18
121
|
const allowed = new Set(tiers);
|
|
@@ -57,26 +160,43 @@ function createSkillHttpHandler(options) {
|
|
|
57
160
|
handlers.set(key, handler);
|
|
58
161
|
return handler;
|
|
59
162
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
token: "",
|
|
75
|
-
clientId: caller.id,
|
|
76
|
-
scopes: [],
|
|
77
|
-
extra: { ...caller.claims }
|
|
163
|
+
const limit = options.rateLimit ? createRateLimiter(options.rateLimit) : void 0;
|
|
164
|
+
const serve = async (request) => {
|
|
165
|
+
const capped = await capBody(
|
|
166
|
+
request,
|
|
167
|
+
options.maxRequestBytes ?? DEFAULT_MAX_REQUEST_BYTES
|
|
168
|
+
);
|
|
169
|
+
if (capped instanceof Response) return capped;
|
|
170
|
+
const caller = await options.authorize(capped);
|
|
171
|
+
if (!caller) {
|
|
172
|
+
return new Response(JSON.stringify({ error: "unauthorized" }), {
|
|
173
|
+
status: 401,
|
|
174
|
+
headers: {
|
|
175
|
+
"content-type": "application/json",
|
|
176
|
+
"www-authenticate": options.challenge ?? "Bearer"
|
|
78
177
|
}
|
|
79
178
|
});
|
|
179
|
+
}
|
|
180
|
+
const limited = limit?.(caller.id);
|
|
181
|
+
if (limited) return limited;
|
|
182
|
+
return handlerFor(tiersFor(caller)).fetch(capped, {
|
|
183
|
+
authInfo: {
|
|
184
|
+
token: "",
|
|
185
|
+
clientId: caller.id,
|
|
186
|
+
scopes: [],
|
|
187
|
+
extra: { ...caller.claims }
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
return {
|
|
192
|
+
async fetch(request) {
|
|
193
|
+
const wrongHost = checkHost(request, options.allowedHosts);
|
|
194
|
+
if (wrongHost) return wrongHost;
|
|
195
|
+
const origin = checkOrigin(request, options.allowedOrigins);
|
|
196
|
+
if (origin.kind === "refused") return origin.response;
|
|
197
|
+
if (origin.kind !== "allowed") return serve(request);
|
|
198
|
+
if (request.method === "OPTIONS") return preflight(origin.origin);
|
|
199
|
+
return withHeaders(await serve(request), corsHeaders(origin.origin));
|
|
80
200
|
},
|
|
81
201
|
reload() {
|
|
82
202
|
all = load(options);
|
|
@@ -88,6 +208,7 @@ function createSkillHttpHandler(options) {
|
|
|
88
208
|
};
|
|
89
209
|
}
|
|
90
210
|
export {
|
|
211
|
+
DEFAULT_MAX_REQUEST_BYTES,
|
|
91
212
|
DEFAULT_MODEL_ID,
|
|
92
213
|
DEFAULT_TIERS,
|
|
93
214
|
createSkillHttpHandler,
|
package/dist/index.d.ts
CHANGED
|
@@ -304,7 +304,7 @@ declare function resolveSkillFileUri(uri: string, entries: readonly SkillEntry[]
|
|
|
304
304
|
|
|
305
305
|
/** Reported to the host on connect. */
|
|
306
306
|
declare const SERVER_NAME = "pmcp";
|
|
307
|
-
declare const SERVER_VERSION = "0.8.
|
|
307
|
+
declare const SERVER_VERSION = "0.8.1";
|
|
308
308
|
/**
|
|
309
309
|
* Registers the skill_* tools on a server.
|
|
310
310
|
*
|
package/dist/index.js
CHANGED
|
@@ -14,11 +14,11 @@ import {
|
|
|
14
14
|
readSpec,
|
|
15
15
|
validateCatalog,
|
|
16
16
|
validateSkillDir
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-DTQAN33I.js";
|
|
18
18
|
import {
|
|
19
19
|
DEFAULT_INTENT_CACHE_LIMIT,
|
|
20
20
|
openIntentCache
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-MO3LLVVY.js";
|
|
22
22
|
import {
|
|
23
23
|
ASSET_KINDS,
|
|
24
24
|
CATALOG_PAGE_MAX,
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
skillFileUri,
|
|
71
71
|
specProblems,
|
|
72
72
|
withoutBody
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-HB4VJ5VW.js";
|
|
74
74
|
export {
|
|
75
75
|
ASSET_KINDS,
|
|
76
76
|
CATALOG_PAGE_MAX,
|
package/dist/stdio.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modootoday/pmcp",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"license": "Elastic-2.0",
|
|
5
5
|
"author": "modootoday",
|
|
6
6
|
"description": "An MCP server that finds the skills your installed packages already ship. Reads SKILL.md out of node_modules by shape rather than by path, so it sees the skills, .agent, .claude, .gemini and .codex conventions alike, and keeps session startup flat as the number of installed skills grows.",
|