@knitli/openapi-mcp 0.0.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/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +362 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +249 -0
- package/dist/compile.d.ts +14 -0
- package/dist/compile.js +132 -0
- package/dist/compiler.d.ts +13 -0
- package/dist/compiler.js +7 -0
- package/dist/conformance/fixtures.d.ts +24 -0
- package/dist/conformance/fixtures.js +119 -0
- package/dist/conformance/index.d.ts +2 -0
- package/dist/conformance/index.js +2 -0
- package/dist/conformance/runtime-suite.d.ts +31 -0
- package/dist/conformance/runtime-suite.js +343 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/load.d.ts +31 -0
- package/dist/load.js +20 -0
- package/dist/operations.d.ts +27 -0
- package/dist/operations.js +231 -0
- package/dist/permissions.d.ts +31 -0
- package/dist/permissions.js +89 -0
- package/dist/release/compile-release.d.ts +33 -0
- package/dist/release/compile-release.js +1620 -0
- package/dist/release/load-v4.d.ts +29 -0
- package/dist/release/load-v4.js +297 -0
- package/dist/release/manifest-builder.d.ts +19 -0
- package/dist/release/manifest-builder.js +28 -0
- package/dist/release/publish.d.ts +45 -0
- package/dist/release/publish.js +346 -0
- package/dist/release/reference-map.d.ts +53 -0
- package/dist/release/reference-map.js +347 -0
- package/dist/release/schema-v4.d.ts +5 -0
- package/dist/release/schema-v4.js +91 -0
- package/dist/runtime/action-permit.d.ts +11 -0
- package/dist/runtime/action-permit.js +1 -0
- package/dist/runtime/classify.d.ts +16 -0
- package/dist/runtime/classify.js +279 -0
- package/dist/runtime/credential-binding.d.ts +3 -0
- package/dist/runtime/credential-binding.js +101 -0
- package/dist/runtime/digest.d.ts +8 -0
- package/dist/runtime/digest.js +80 -0
- package/dist/runtime/errors.d.ts +14 -0
- package/dist/runtime/errors.js +15 -0
- package/dist/runtime/index.d.ts +20 -0
- package/dist/runtime/index.js +14 -0
- package/dist/runtime/manifest.d.ts +33 -0
- package/dist/runtime/manifest.js +489 -0
- package/dist/runtime/prepared-call.d.ts +39 -0
- package/dist/runtime/prepared-call.js +566 -0
- package/dist/runtime/references.d.ts +7 -0
- package/dist/runtime/references.js +197 -0
- package/dist/runtime/runtime.d.ts +25 -0
- package/dist/runtime/runtime.js +1272 -0
- package/dist/runtime/schema-keywords.d.ts +2 -0
- package/dist/runtime/schema-keywords.js +30 -0
- package/dist/runtime/schema-resolver.d.ts +10 -0
- package/dist/runtime/schema-resolver.js +237 -0
- package/dist/runtime/serialize.d.ts +12 -0
- package/dist/runtime/serialize.js +877 -0
- package/dist/runtime/store.d.ts +52 -0
- package/dist/runtime/store.js +582 -0
- package/dist/runtime/strict-json.d.ts +19 -0
- package/dist/runtime/strict-json.js +539 -0
- package/dist/runtime/types.d.ts +457 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/verify-record.d.ts +6 -0
- package/dist/runtime/verify-record.js +492 -0
- package/dist/runtime/versions.d.ts +63 -0
- package/dist/runtime/versions.js +86 -0
- package/dist/safety.d.ts +28 -0
- package/dist/safety.js +60 -0
- package/dist/schema.d.ts +5 -0
- package/dist/schema.js +52 -0
- package/dist/schemas.d.ts +9 -0
- package/dist/schemas.js +16 -0
- package/dist/sign.d.ts +20 -0
- package/dist/sign.js +55 -0
- package/dist/sqlite/auth.d.ts +33 -0
- package/dist/sqlite/auth.js +973 -0
- package/dist/sqlite/catalog-store.d.ts +20 -0
- package/dist/sqlite/catalog-store.js +370 -0
- package/dist/sqlite/destination-guard.d.ts +24 -0
- package/dist/sqlite/destination-guard.js +140 -0
- package/dist/sqlite/generation-store.d.ts +12 -0
- package/dist/sqlite/generation-store.js +721 -0
- package/dist/sqlite/guarded-fetch.d.ts +24 -0
- package/dist/sqlite/guarded-fetch.js +626 -0
- package/dist/sqlite/index.d.ts +5 -0
- package/dist/sqlite/index.js +5 -0
- package/dist/sqlite/oauth-resource.d.ts +2 -0
- package/dist/sqlite/oauth-resource.js +15 -0
- package/dist/sqlite/release-completion.d.ts +5 -0
- package/dist/sqlite/release-completion.js +80 -0
- package/dist/stdio/action-broker.d.ts +15 -0
- package/dist/stdio/action-broker.js +136 -0
- package/dist/stdio/authorizer.d.ts +30 -0
- package/dist/stdio/authorizer.js +484 -0
- package/dist/stdio/config.d.ts +77 -0
- package/dist/stdio/config.js +144 -0
- package/dist/stdio/exact-policy.d.ts +41 -0
- package/dist/stdio/exact-policy.js +361 -0
- package/dist/stdio/index.d.ts +6 -0
- package/dist/stdio/index.js +148 -0
- package/dist/stdio/render.d.ts +8 -0
- package/dist/stdio/render.js +253 -0
- package/dist/stdio/request-state.d.ts +18 -0
- package/dist/stdio/request-state.js +79 -0
- package/dist/stdio/search.d.ts +10 -0
- package/dist/stdio/search.js +76 -0
- package/dist/stdio/server.d.ts +29 -0
- package/dist/stdio/server.js +309 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +1 -0
- package/package.json +131 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type BigIntStats } from "node:fs";
|
|
2
|
+
import type { ManifestEnvelope, RuntimeLimits } from "../runtime/index.ts";
|
|
3
|
+
export declare function releaseFileIdentity(path: string): BigIntStats;
|
|
4
|
+
/** Filesystem completion is local ingress policy, never a portable semantic-store requirement. */
|
|
5
|
+
export declare function verifyReleaseCompletion(path: string, payload: BigIntStats, envelope: ManifestEnvelope, limits: RuntimeLimits): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, } from "node:fs";
|
|
2
|
+
import { OpenApiMcpError } from "../runtime/errors.js";
|
|
3
|
+
import { canonicalJson, parseJsonStrict } from "../runtime/strict-json.js";
|
|
4
|
+
export function releaseFileIdentity(path) {
|
|
5
|
+
const value = lstatSync(path, { bigint: true });
|
|
6
|
+
if (!value.isFile() || value.isSymbolicLink())
|
|
7
|
+
throw new OpenApiMcpError("MANIFEST_INVALID");
|
|
8
|
+
return value;
|
|
9
|
+
}
|
|
10
|
+
function sameFile(left, right) {
|
|
11
|
+
return (left.dev === right.dev &&
|
|
12
|
+
left.ino === right.ino &&
|
|
13
|
+
left.birthtimeNs === right.birthtimeNs);
|
|
14
|
+
}
|
|
15
|
+
function sameSnapshot(left, right) {
|
|
16
|
+
return (sameFile(left, right) &&
|
|
17
|
+
left.size === right.size &&
|
|
18
|
+
left.mtimeNs === right.mtimeNs &&
|
|
19
|
+
left.ctimeNs === right.ctimeNs);
|
|
20
|
+
}
|
|
21
|
+
function readCompleteSidecar(path, maximum) {
|
|
22
|
+
const before = releaseFileIdentity(path);
|
|
23
|
+
if (before.size > BigInt(maximum) || !constants.O_NOFOLLOW)
|
|
24
|
+
throw new Error();
|
|
25
|
+
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
26
|
+
try {
|
|
27
|
+
const opened = fstatSync(fd, { bigint: true });
|
|
28
|
+
if (!sameSnapshot(before, opened) || !opened.isFile())
|
|
29
|
+
throw new Error();
|
|
30
|
+
const bytes = Buffer.alloc(Number(before.size) + 1);
|
|
31
|
+
let count = 0;
|
|
32
|
+
while (count < bytes.length) {
|
|
33
|
+
const read = readSync(fd, bytes, count, bytes.length - count, null);
|
|
34
|
+
if (read === 0)
|
|
35
|
+
break;
|
|
36
|
+
count += read;
|
|
37
|
+
}
|
|
38
|
+
const after = fstatSync(fd, { bigint: true });
|
|
39
|
+
const named = releaseFileIdentity(path);
|
|
40
|
+
if (count !== Number(before.size) ||
|
|
41
|
+
!sameSnapshot(before, after) ||
|
|
42
|
+
!sameSnapshot(before, named))
|
|
43
|
+
throw new Error();
|
|
44
|
+
return {
|
|
45
|
+
text: new TextDecoder("utf-8", { fatal: true }).decode(bytes.subarray(0, count)),
|
|
46
|
+
identity: before,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
closeSync(fd);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Filesystem completion is local ingress policy, never a portable semantic-store requirement. */
|
|
54
|
+
export function verifyReleaseCompletion(path, payload, envelope, limits) {
|
|
55
|
+
try {
|
|
56
|
+
if (!path.endsWith(".sqlite") ||
|
|
57
|
+
!sameFile(payload, releaseFileIdentity(path)))
|
|
58
|
+
throw new Error();
|
|
59
|
+
const stem = path.slice(0, -".sqlite".length);
|
|
60
|
+
const manifest = readCompleteSidecar(`${stem}.manifest.json`, limits.maxManifestBytes);
|
|
61
|
+
const signatureFile = readCompleteSidecar(`${stem}.manifest.sig`, 16 * 1024);
|
|
62
|
+
const signature = parseJsonStrict(signatureFile.text, {
|
|
63
|
+
maxBytes: 16 * 1024,
|
|
64
|
+
maxDepth: 4,
|
|
65
|
+
maxKeys: 8,
|
|
66
|
+
});
|
|
67
|
+
if (manifest.text !== envelope.manifestJson ||
|
|
68
|
+
canonicalJson(signature) !==
|
|
69
|
+
canonicalJson(envelope.signature))
|
|
70
|
+
throw new Error();
|
|
71
|
+
if (!sameSnapshot(manifest.identity, releaseFileIdentity(`${stem}.manifest.json`)) ||
|
|
72
|
+
!sameSnapshot(signatureFile.identity, releaseFileIdentity(`${stem}.manifest.sig`)))
|
|
73
|
+
throw new Error();
|
|
74
|
+
if (!sameFile(payload, releaseFileIdentity(path)))
|
|
75
|
+
throw new Error();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new OpenApiMcpError("MANIFEST_INVALID", "Release publication is incomplete or mismatched");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ActionDispatchPermit } from "../runtime/action-permit.ts";
|
|
2
|
+
import type { ActionAuthorizer, AuthorizedActionDecision, CredentialAuthorizationBinding, PreparedCall, Sha256 } from "../runtime/types.ts";
|
|
3
|
+
interface BoundaryOptions {
|
|
4
|
+
readonly now?: () => number;
|
|
5
|
+
}
|
|
6
|
+
/** Package-private: intentionally absent from every public barrel export. */
|
|
7
|
+
export declare function createActionAuthorizationBoundary(authorizer: ActionAuthorizer, options?: BoundaryOptions): {
|
|
8
|
+
readonly broker: {
|
|
9
|
+
consume(decision: AuthorizedActionDecision, call: PreparedCall, binding: CredentialAuthorizationBinding): Promise<ActionDispatchPermit>;
|
|
10
|
+
};
|
|
11
|
+
readonly permits: {
|
|
12
|
+
consume(permit: ActionDispatchPermit, callDigest: Sha256, bindingDigest: Sha256): void;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { snapshotCredentialAuthorizationBinding } from "../runtime/credential-binding.js";
|
|
2
|
+
import { OpenApiMcpError } from "../runtime/errors.js";
|
|
3
|
+
import { verifyAndSnapshotPreparedCall } from "../runtime/prepared-call.js";
|
|
4
|
+
const permitTtlMs = 120_000;
|
|
5
|
+
const digestPattern = /^[0-9a-f]{64}$/;
|
|
6
|
+
const baseDecisionKeys = [
|
|
7
|
+
"authorizationId",
|
|
8
|
+
"callDigest",
|
|
9
|
+
"credentialBindingDigest",
|
|
10
|
+
"path",
|
|
11
|
+
"status",
|
|
12
|
+
];
|
|
13
|
+
function denied() {
|
|
14
|
+
return new OpenApiMcpError("ACTION_DENIED", "Action authorization denied");
|
|
15
|
+
}
|
|
16
|
+
function snapshotDecision(value) {
|
|
17
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
18
|
+
throw denied();
|
|
19
|
+
}
|
|
20
|
+
const prototype = Object.getPrototypeOf(value);
|
|
21
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
22
|
+
throw denied();
|
|
23
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
24
|
+
if (ownKeys.some((key) => typeof key !== "string"))
|
|
25
|
+
throw denied();
|
|
26
|
+
const keys = ownKeys;
|
|
27
|
+
const allowed = [...baseDecisionKeys, "policyDigest"];
|
|
28
|
+
if (keys.some((key) => !allowed.includes(key)) ||
|
|
29
|
+
baseDecisionKeys.some((key) => !keys.includes(key))) {
|
|
30
|
+
throw denied();
|
|
31
|
+
}
|
|
32
|
+
const fields = Object.create(null);
|
|
33
|
+
for (const key of keys) {
|
|
34
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
35
|
+
if (descriptor === undefined ||
|
|
36
|
+
!("value" in descriptor) ||
|
|
37
|
+
!descriptor.enumerable) {
|
|
38
|
+
throw denied();
|
|
39
|
+
}
|
|
40
|
+
fields[key] = descriptor.value;
|
|
41
|
+
}
|
|
42
|
+
if (fields.status !== "authorized" ||
|
|
43
|
+
(fields.path !== "per-call" && fields.path !== "exact-policy") ||
|
|
44
|
+
typeof fields.callDigest !== "string" ||
|
|
45
|
+
!digestPattern.test(fields.callDigest) ||
|
|
46
|
+
typeof fields.credentialBindingDigest !== "string" ||
|
|
47
|
+
!digestPattern.test(fields.credentialBindingDigest) ||
|
|
48
|
+
typeof fields.authorizationId !== "object" ||
|
|
49
|
+
fields.authorizationId === null) {
|
|
50
|
+
throw denied();
|
|
51
|
+
}
|
|
52
|
+
if ((fields.path === "exact-policy" &&
|
|
53
|
+
(typeof fields.policyDigest !== "string" ||
|
|
54
|
+
!digestPattern.test(fields.policyDigest))) ||
|
|
55
|
+
(fields.path === "per-call" && "policyDigest" in fields)) {
|
|
56
|
+
throw denied();
|
|
57
|
+
}
|
|
58
|
+
const snapshot = {
|
|
59
|
+
status: "authorized",
|
|
60
|
+
callDigest: fields.callDigest,
|
|
61
|
+
credentialBindingDigest: fields.credentialBindingDigest,
|
|
62
|
+
path: fields.path,
|
|
63
|
+
authorizationId: fields.authorizationId,
|
|
64
|
+
...(fields.path === "exact-policy"
|
|
65
|
+
? { policyDigest: fields.policyDigest }
|
|
66
|
+
: {}),
|
|
67
|
+
};
|
|
68
|
+
return Object.freeze(snapshot);
|
|
69
|
+
}
|
|
70
|
+
function sameTuple(decision, call, credential) {
|
|
71
|
+
return (decision.callDigest === call.preparedCallDigest &&
|
|
72
|
+
decision.credentialBindingDigest === credential.bindingDigest &&
|
|
73
|
+
credential.profileId === call.credentialProfileId &&
|
|
74
|
+
credential.profileDigest === call.credentialProfileDigest &&
|
|
75
|
+
credential.slotsDigest === call.reservedSlotsDigest);
|
|
76
|
+
}
|
|
77
|
+
/** Package-private: intentionally absent from every public barrel export. */
|
|
78
|
+
export function createActionAuthorizationBoundary(authorizer, options = {}) {
|
|
79
|
+
const now = options.now ?? Date.now;
|
|
80
|
+
const ownedPermits = new WeakMap();
|
|
81
|
+
return Object.freeze({
|
|
82
|
+
broker: Object.freeze({
|
|
83
|
+
async consume(untrustedDecision, untrustedCall, untrustedBinding) {
|
|
84
|
+
try {
|
|
85
|
+
// Check immutability before snapshotting: a successful frozen check
|
|
86
|
+
// leaves even a Proxy target structurally stable for the subsequent
|
|
87
|
+
// own-data validation. Mutable decisions never cross the await.
|
|
88
|
+
const preserveDecisionIdentity = typeof untrustedDecision === "object" &&
|
|
89
|
+
untrustedDecision !== null &&
|
|
90
|
+
Object.isFrozen(untrustedDecision);
|
|
91
|
+
const decision = snapshotDecision(untrustedDecision);
|
|
92
|
+
const callPromise = verifyAndSnapshotPreparedCall(untrustedCall);
|
|
93
|
+
const credentialPromise = snapshotCredentialAuthorizationBinding(untrustedBinding);
|
|
94
|
+
const [call, credential] = await Promise.all([
|
|
95
|
+
callPromise,
|
|
96
|
+
credentialPromise,
|
|
97
|
+
]);
|
|
98
|
+
if (!sameTuple(decision, call, credential))
|
|
99
|
+
throw denied();
|
|
100
|
+
await authorizer.consume(preserveDecisionIdentity ? untrustedDecision : decision, call, credential);
|
|
101
|
+
const issuedAt = now();
|
|
102
|
+
if (!Number.isFinite(issuedAt))
|
|
103
|
+
throw denied();
|
|
104
|
+
const permit = Object.freeze(Object.create(null));
|
|
105
|
+
ownedPermits.set(permit, Object.freeze({
|
|
106
|
+
callDigest: call.preparedCallDigest,
|
|
107
|
+
credentialBindingDigest: credential.bindingDigest,
|
|
108
|
+
expiresAt: issuedAt + permitTtlMs,
|
|
109
|
+
}));
|
|
110
|
+
return permit;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
throw denied();
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
permits: Object.freeze({
|
|
118
|
+
consume(permit, callDigest, bindingDigest) {
|
|
119
|
+
if (typeof permit !== "object" || permit === null)
|
|
120
|
+
throw denied();
|
|
121
|
+
const tuple = ownedPermits.get(permit);
|
|
122
|
+
if (tuple === undefined ||
|
|
123
|
+
tuple.callDigest !== callDigest ||
|
|
124
|
+
tuple.credentialBindingDigest !== bindingDigest) {
|
|
125
|
+
throw denied();
|
|
126
|
+
}
|
|
127
|
+
const currentTime = now();
|
|
128
|
+
if (!Number.isFinite(currentTime) || currentTime >= tuple.expiresAt) {
|
|
129
|
+
throw denied();
|
|
130
|
+
}
|
|
131
|
+
// No await can intervene between the successful check and deletion.
|
|
132
|
+
ownedPermits.delete(permit);
|
|
133
|
+
},
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type ServerContext } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { ActionAuthorizer, VerifiedActionRequestState } from "../runtime/types.ts";
|
|
3
|
+
import { type CompiledExactPolicy } from "./exact-policy.ts";
|
|
4
|
+
interface InternalLimits {
|
|
5
|
+
readonly pending: number;
|
|
6
|
+
readonly receipts: number;
|
|
7
|
+
readonly activations: number;
|
|
8
|
+
}
|
|
9
|
+
export interface AuthorizerTestOptions {
|
|
10
|
+
/** Test seam. Production callers should leave this unset. */
|
|
11
|
+
readonly now?: () => number;
|
|
12
|
+
/** Test seam. Production callers should leave this unset. */
|
|
13
|
+
readonly randomBytes?: (size: number) => Uint8Array;
|
|
14
|
+
/** Test seam; values may only lower the production bounds. */
|
|
15
|
+
readonly limits?: Readonly<Partial<InternalLimits>>;
|
|
16
|
+
}
|
|
17
|
+
export interface ClientElicitationAuthorizerOptions extends AuthorizerTestOptions {
|
|
18
|
+
readonly elicitationAvailable: () => boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface ExactPolicyAuthorizerOptions extends AuthorizerTestOptions {
|
|
21
|
+
readonly templates: readonly CompiledExactPolicy[];
|
|
22
|
+
readonly elicitationAvailable: () => boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface StatefulActionAuthorizer extends ActionAuthorizer {
|
|
25
|
+
readonly requestStateVerifier: (state: string, context?: ServerContext) => Promise<VerifiedActionRequestState>;
|
|
26
|
+
}
|
|
27
|
+
export declare function createDenyActionAuthorizer(): ActionAuthorizer;
|
|
28
|
+
export declare function createClientElicitationActionAuthorizer(options: ClientElicitationAuthorizerOptions): StatefulActionAuthorizer;
|
|
29
|
+
export declare function createExactPolicyActionAuthorizer(options: ExactPolicyAuthorizerOptions): StatefulActionAuthorizer;
|
|
30
|
+
export {};
|