@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,144 @@
|
|
|
1
|
+
import { isAbsolute } from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { parseJsonStrict } from "../runtime/strict-json.js";
|
|
4
|
+
import { DEFAULT_RUNTIME_LIMITS, resolveRuntimeLimits, } from "../runtime/versions.js";
|
|
5
|
+
import { validateLocalAuthProfile } from "../sqlite/auth.js";
|
|
6
|
+
import { isOAuthResource } from "../sqlite/oauth-resource.js";
|
|
7
|
+
const id = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/);
|
|
8
|
+
const path = z.string().min(1).refine(isAbsolute);
|
|
9
|
+
const https = z.string().refine((value) => {
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(value);
|
|
12
|
+
return (url.protocol === "https:" && !url.username && !url.password && !url.hash);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
const origin = https.refine((value) => new URL(value).origin === value);
|
|
19
|
+
const scopes = z.array(z.string().min(1).max(256)).max(256);
|
|
20
|
+
const auth = z.discriminatedUnion("type", [
|
|
21
|
+
z.strictObject({
|
|
22
|
+
type: z.literal("bearer-env"),
|
|
23
|
+
env: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/),
|
|
24
|
+
}),
|
|
25
|
+
z.strictObject({
|
|
26
|
+
type: z.literal("api-key-env"),
|
|
27
|
+
env: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/),
|
|
28
|
+
placement: z.enum(["header", "query"]),
|
|
29
|
+
name: z.string().min(1).max(256),
|
|
30
|
+
}),
|
|
31
|
+
z.strictObject({
|
|
32
|
+
type: z.literal("oauth2-pkce"),
|
|
33
|
+
authorizationEndpoint: https,
|
|
34
|
+
tokenEndpoint: https,
|
|
35
|
+
clientId: z.string().min(1).max(1024),
|
|
36
|
+
scopes,
|
|
37
|
+
resource: z.string().refine(isOAuthResource).optional(),
|
|
38
|
+
}),
|
|
39
|
+
]);
|
|
40
|
+
const profile = z.strictObject({
|
|
41
|
+
profileId: id,
|
|
42
|
+
revision: z.number().int().positive(),
|
|
43
|
+
allowedOrigins: z.array(origin).min(1).max(256),
|
|
44
|
+
auth,
|
|
45
|
+
audience: z.string().min(1).max(2048).optional(),
|
|
46
|
+
scopes: scopes.optional(),
|
|
47
|
+
});
|
|
48
|
+
const key = z.strictObject({
|
|
49
|
+
issuer: z
|
|
50
|
+
.string()
|
|
51
|
+
.min(1)
|
|
52
|
+
.max(256)
|
|
53
|
+
.regex(/^[\x21-\x7e]+$/),
|
|
54
|
+
keyId: id,
|
|
55
|
+
publicKey: z
|
|
56
|
+
.string()
|
|
57
|
+
.regex(/^[A-Za-z0-9_-]+$/)
|
|
58
|
+
.max(4096),
|
|
59
|
+
});
|
|
60
|
+
const digest = z.string().regex(/^[0-9a-f]{64}$/);
|
|
61
|
+
const constraint = z.lazy(() => z.discriminatedUnion("kind", [
|
|
62
|
+
z.strictObject({ kind: z.literal("exact"), value: z.json() }),
|
|
63
|
+
z.strictObject({
|
|
64
|
+
kind: z.literal("string-set"),
|
|
65
|
+
values: z.array(z.string()).min(1).max(256),
|
|
66
|
+
}),
|
|
67
|
+
z.strictObject({
|
|
68
|
+
kind: z.literal("number"),
|
|
69
|
+
min: z.number(),
|
|
70
|
+
max: z.number(),
|
|
71
|
+
}),
|
|
72
|
+
z.strictObject({
|
|
73
|
+
kind: z.literal("object"),
|
|
74
|
+
properties: z.record(z.string(), constraint),
|
|
75
|
+
}),
|
|
76
|
+
z.strictObject({
|
|
77
|
+
kind: z.literal("array"),
|
|
78
|
+
maxItems: z.number().int().nonnegative(),
|
|
79
|
+
items: constraint,
|
|
80
|
+
}),
|
|
81
|
+
]));
|
|
82
|
+
const exactPolicy = z.strictObject({
|
|
83
|
+
version: z.literal(1),
|
|
84
|
+
catalogId: id,
|
|
85
|
+
releaseId: id,
|
|
86
|
+
manifestDigest: digest,
|
|
87
|
+
operationId: z.string().startsWith("operation:"),
|
|
88
|
+
operationDigest: digest,
|
|
89
|
+
credentialProfileDigest: digest,
|
|
90
|
+
actionKind: z.enum(["create", "update"]),
|
|
91
|
+
cardinality: z.enum(["single", "bounded"]),
|
|
92
|
+
maxAffected: z.number().int().positive(),
|
|
93
|
+
expiresAt: z.number().int().positive(),
|
|
94
|
+
arguments: constraint,
|
|
95
|
+
});
|
|
96
|
+
const limits = z.strictObject(Object.fromEntries(Object.entries(DEFAULT_RUNTIME_LIMITS).map(([name, maximum]) => [
|
|
97
|
+
name,
|
|
98
|
+
z.number().int().min(1).max(maximum).optional(),
|
|
99
|
+
])));
|
|
100
|
+
const schema = z.strictObject({
|
|
101
|
+
version: z.literal(1),
|
|
102
|
+
generationStatePath: path,
|
|
103
|
+
catalogs: z
|
|
104
|
+
.array(z.strictObject({ catalogId: id, releaseId: id, path, profileId: id }))
|
|
105
|
+
.min(1)
|
|
106
|
+
.max(256),
|
|
107
|
+
trust: z.strictObject({
|
|
108
|
+
releaseKeys: z.array(key).min(1).max(256),
|
|
109
|
+
rollbackKeys: z.array(key).max(256),
|
|
110
|
+
}),
|
|
111
|
+
allowedOrigins: z.array(origin).min(1).max(256),
|
|
112
|
+
profiles: z.array(profile).min(1).max(256),
|
|
113
|
+
limits: limits.optional(),
|
|
114
|
+
exactPolicies: z.array(exactPolicy).max(256).optional(),
|
|
115
|
+
});
|
|
116
|
+
/** Operator-only input. Error text never contains submitted values or keys. */
|
|
117
|
+
export function parseOpenApiStdioConfig(input) {
|
|
118
|
+
try {
|
|
119
|
+
const value = typeof input === "string"
|
|
120
|
+
? parseJsonStrict(input, {
|
|
121
|
+
maxBytes: 1024 * 1024,
|
|
122
|
+
maxDepth: 40,
|
|
123
|
+
maxKeys: 30_000,
|
|
124
|
+
})
|
|
125
|
+
: input;
|
|
126
|
+
const config = schema.parse(value);
|
|
127
|
+
for (const entry of config.profiles)
|
|
128
|
+
validateLocalAuthProfile(entry);
|
|
129
|
+
const profileIds = new Set(config.profiles.map((entry) => entry.profileId));
|
|
130
|
+
if (profileIds.size !== config.profiles.length ||
|
|
131
|
+
new Set(config.catalogs.map((entry) => entry.catalogId)).size !==
|
|
132
|
+
config.catalogs.length)
|
|
133
|
+
throw new Error();
|
|
134
|
+
if (config.catalogs.some((entry) => !profileIds.has(entry.profileId)))
|
|
135
|
+
throw new Error();
|
|
136
|
+
if (config.profiles.some((entry) => entry.allowedOrigins.some((allowed) => !config.allowedOrigins.includes(allowed))))
|
|
137
|
+
throw new Error();
|
|
138
|
+
resolveRuntimeLimits(config.limits);
|
|
139
|
+
return config;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
throw new Error("Invalid OpenAPI stdio configuration");
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CatalogId, JsonValue, PreparedCall, ReleaseId, Sha256, TypedOperationId } from "../runtime/types.ts";
|
|
2
|
+
export type ArgumentConstraint = {
|
|
3
|
+
readonly kind: "exact";
|
|
4
|
+
readonly value: JsonValue;
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: "string-set";
|
|
7
|
+
readonly values: readonly string[];
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: "number";
|
|
10
|
+
readonly min: number;
|
|
11
|
+
readonly max: number;
|
|
12
|
+
} | {
|
|
13
|
+
readonly kind: "object";
|
|
14
|
+
readonly properties: Readonly<Record<string, ArgumentConstraint>>;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: "array";
|
|
17
|
+
readonly maxItems: number;
|
|
18
|
+
readonly items: ArgumentConstraint;
|
|
19
|
+
};
|
|
20
|
+
export interface ExactPolicyTemplate {
|
|
21
|
+
readonly version: 1;
|
|
22
|
+
readonly catalogId: CatalogId;
|
|
23
|
+
readonly releaseId: ReleaseId;
|
|
24
|
+
readonly manifestDigest: Sha256;
|
|
25
|
+
readonly operationId: TypedOperationId;
|
|
26
|
+
readonly operationDigest: Sha256;
|
|
27
|
+
readonly credentialProfileDigest: Sha256;
|
|
28
|
+
readonly actionKind: "create" | "update";
|
|
29
|
+
readonly cardinality: "single" | "bounded";
|
|
30
|
+
readonly maxAffected: number;
|
|
31
|
+
readonly expiresAt: number;
|
|
32
|
+
readonly arguments: ArgumentConstraint;
|
|
33
|
+
}
|
|
34
|
+
export interface CompiledExactPolicy {
|
|
35
|
+
readonly template: Readonly<ExactPolicyTemplate>;
|
|
36
|
+
readonly policyDigest: Sha256;
|
|
37
|
+
}
|
|
38
|
+
/** Validate and normalize an untrusted startup template before hashing it. */
|
|
39
|
+
export declare function compileExactPolicy(input: unknown): Promise<CompiledExactPolicy>;
|
|
40
|
+
/** Return true only when every policy identity and closed constraint matches. */
|
|
41
|
+
export declare function matchesExactPolicy(policy: CompiledExactPolicy, call: PreparedCall, nowMs: number): boolean;
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { sha256 } from "../runtime/digest.js";
|
|
2
|
+
import { OpenApiMcpError } from "../runtime/errors.js";
|
|
3
|
+
import { parseTypedRecordId } from "../runtime/references.js";
|
|
4
|
+
import { canonicalJsonBounded, parseJsonStrict, } from "../runtime/strict-json.js";
|
|
5
|
+
const POLICY_DOMAIN = "knitli.openapi-mcp.exact-policy.v1";
|
|
6
|
+
const MAX_POLICY_BYTES = 64 * 1024;
|
|
7
|
+
const MAX_POLICY_DEPTH = 32;
|
|
8
|
+
const MAX_POLICY_NODES = 4_096;
|
|
9
|
+
const MAX_STRING_SET_VALUES = 256;
|
|
10
|
+
const MAX_AFFECTED = 10_000;
|
|
11
|
+
const digestPattern = /^[0-9a-f]{64}$/;
|
|
12
|
+
const shortIdPattern = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
13
|
+
const templateKeys = [
|
|
14
|
+
"actionKind",
|
|
15
|
+
"arguments",
|
|
16
|
+
"cardinality",
|
|
17
|
+
"catalogId",
|
|
18
|
+
"credentialProfileDigest",
|
|
19
|
+
"expiresAt",
|
|
20
|
+
"manifestDigest",
|
|
21
|
+
"maxAffected",
|
|
22
|
+
"operationDigest",
|
|
23
|
+
"operationId",
|
|
24
|
+
"releaseId",
|
|
25
|
+
"version",
|
|
26
|
+
];
|
|
27
|
+
function invalid() {
|
|
28
|
+
return new OpenApiMcpError("INPUT_INVALID", "Exact policy template is invalid");
|
|
29
|
+
}
|
|
30
|
+
function objectWithExactKeys(value, keys) {
|
|
31
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
32
|
+
throw invalid();
|
|
33
|
+
}
|
|
34
|
+
const prototype = Object.getPrototypeOf(value);
|
|
35
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
36
|
+
throw invalid();
|
|
37
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
38
|
+
if (ownKeys.some((key) => typeof key !== "string"))
|
|
39
|
+
throw invalid();
|
|
40
|
+
const stringKeys = ownKeys;
|
|
41
|
+
if (keys !== null &&
|
|
42
|
+
(stringKeys.length !== keys.length ||
|
|
43
|
+
stringKeys.some((key) => !keys.includes(key)))) {
|
|
44
|
+
throw invalid();
|
|
45
|
+
}
|
|
46
|
+
for (const key of stringKeys) {
|
|
47
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
48
|
+
if (descriptor === undefined ||
|
|
49
|
+
!("value" in descriptor) ||
|
|
50
|
+
!descriptor.enumerable) {
|
|
51
|
+
throw invalid();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
function ownValue(object, key) {
|
|
57
|
+
const descriptor = Object.getOwnPropertyDescriptor(object, key);
|
|
58
|
+
if (descriptor === undefined || !("value" in descriptor))
|
|
59
|
+
throw invalid();
|
|
60
|
+
return descriptor.value;
|
|
61
|
+
}
|
|
62
|
+
function shortId(value) {
|
|
63
|
+
if (typeof value !== "string" ||
|
|
64
|
+
value.length === 0 ||
|
|
65
|
+
value.length > 128 ||
|
|
66
|
+
value === "." ||
|
|
67
|
+
value === ".." ||
|
|
68
|
+
!shortIdPattern.test(value)) {
|
|
69
|
+
throw invalid();
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
function digest(value) {
|
|
74
|
+
if (typeof value !== "string" || !digestPattern.test(value))
|
|
75
|
+
throw invalid();
|
|
76
|
+
return value;
|
|
77
|
+
}
|
|
78
|
+
function operationId(value) {
|
|
79
|
+
if (typeof value !== "string")
|
|
80
|
+
throw invalid();
|
|
81
|
+
try {
|
|
82
|
+
const parsed = parseTypedRecordId(value);
|
|
83
|
+
if (!parsed.startsWith("operation:"))
|
|
84
|
+
throw invalid();
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
throw invalid();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function boundedInteger(value, minimum, maximum) {
|
|
92
|
+
if (typeof value !== "number" ||
|
|
93
|
+
!Number.isSafeInteger(value) ||
|
|
94
|
+
value < minimum ||
|
|
95
|
+
value > maximum) {
|
|
96
|
+
throw invalid();
|
|
97
|
+
}
|
|
98
|
+
return value;
|
|
99
|
+
}
|
|
100
|
+
function freezeJson(value) {
|
|
101
|
+
if (Array.isArray(value)) {
|
|
102
|
+
for (const item of value)
|
|
103
|
+
freezeJson(item);
|
|
104
|
+
return Object.freeze(value);
|
|
105
|
+
}
|
|
106
|
+
if (typeof value === "object" && value !== null) {
|
|
107
|
+
for (const child of Object.values(value))
|
|
108
|
+
freezeJson(child);
|
|
109
|
+
return Object.freeze(value);
|
|
110
|
+
}
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
function parseConstraint(value) {
|
|
114
|
+
const source = objectWithExactKeys(value, null);
|
|
115
|
+
const kind = ownValue(source, "kind");
|
|
116
|
+
if (kind === "exact") {
|
|
117
|
+
objectWithExactKeys(source, ["kind", "value"]);
|
|
118
|
+
return Object.freeze({
|
|
119
|
+
kind,
|
|
120
|
+
value: freezeJson(ownValue(source, "value")),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (kind === "string-set") {
|
|
124
|
+
objectWithExactKeys(source, ["kind", "values"]);
|
|
125
|
+
const rawValues = ownValue(source, "values");
|
|
126
|
+
if (!Array.isArray(rawValues) ||
|
|
127
|
+
rawValues.length > MAX_STRING_SET_VALUES ||
|
|
128
|
+
rawValues.some((item) => typeof item !== "string")) {
|
|
129
|
+
throw invalid();
|
|
130
|
+
}
|
|
131
|
+
const values = [...rawValues];
|
|
132
|
+
if (new Set(values).size !== values.length)
|
|
133
|
+
throw invalid();
|
|
134
|
+
values.sort();
|
|
135
|
+
return Object.freeze({ kind, values: Object.freeze(values) });
|
|
136
|
+
}
|
|
137
|
+
if (kind === "number") {
|
|
138
|
+
objectWithExactKeys(source, ["kind", "max", "min"]);
|
|
139
|
+
const min = ownValue(source, "min");
|
|
140
|
+
const max = ownValue(source, "max");
|
|
141
|
+
if (typeof min !== "number" ||
|
|
142
|
+
typeof max !== "number" ||
|
|
143
|
+
!Number.isFinite(min) ||
|
|
144
|
+
!Number.isFinite(max) ||
|
|
145
|
+
min > max) {
|
|
146
|
+
throw invalid();
|
|
147
|
+
}
|
|
148
|
+
return Object.freeze({ kind, min, max });
|
|
149
|
+
}
|
|
150
|
+
if (kind === "object") {
|
|
151
|
+
objectWithExactKeys(source, ["kind", "properties"]);
|
|
152
|
+
const rawProperties = objectWithExactKeys(ownValue(source, "properties"), null);
|
|
153
|
+
const properties = Object.create(null);
|
|
154
|
+
for (const key of Object.keys(rawProperties).sort()) {
|
|
155
|
+
properties[key] = parseConstraint(ownValue(rawProperties, key));
|
|
156
|
+
}
|
|
157
|
+
return Object.freeze({ kind, properties: Object.freeze(properties) });
|
|
158
|
+
}
|
|
159
|
+
if (kind === "array") {
|
|
160
|
+
objectWithExactKeys(source, ["items", "kind", "maxItems"]);
|
|
161
|
+
return Object.freeze({
|
|
162
|
+
kind,
|
|
163
|
+
maxItems: boundedInteger(ownValue(source, "maxItems"), 0, MAX_AFFECTED),
|
|
164
|
+
items: parseConstraint(ownValue(source, "items")),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
throw invalid();
|
|
168
|
+
}
|
|
169
|
+
function parseTemplate(value) {
|
|
170
|
+
const source = objectWithExactKeys(value, templateKeys);
|
|
171
|
+
const version = ownValue(source, "version");
|
|
172
|
+
const actionKind = ownValue(source, "actionKind");
|
|
173
|
+
const cardinality = ownValue(source, "cardinality");
|
|
174
|
+
const maxAffected = boundedInteger(ownValue(source, "maxAffected"), 1, MAX_AFFECTED);
|
|
175
|
+
const expiresAt = ownValue(source, "expiresAt");
|
|
176
|
+
if (version !== 1 ||
|
|
177
|
+
(actionKind !== "create" && actionKind !== "update") ||
|
|
178
|
+
(cardinality !== "single" && cardinality !== "bounded") ||
|
|
179
|
+
(cardinality === "single" && maxAffected !== 1) ||
|
|
180
|
+
typeof expiresAt !== "number" ||
|
|
181
|
+
!Number.isSafeInteger(expiresAt)) {
|
|
182
|
+
throw invalid();
|
|
183
|
+
}
|
|
184
|
+
return Object.freeze({
|
|
185
|
+
version,
|
|
186
|
+
catalogId: shortId(ownValue(source, "catalogId")),
|
|
187
|
+
releaseId: shortId(ownValue(source, "releaseId")),
|
|
188
|
+
manifestDigest: digest(ownValue(source, "manifestDigest")),
|
|
189
|
+
operationId: operationId(ownValue(source, "operationId")),
|
|
190
|
+
operationDigest: digest(ownValue(source, "operationDigest")),
|
|
191
|
+
credentialProfileDigest: digest(ownValue(source, "credentialProfileDigest")),
|
|
192
|
+
actionKind,
|
|
193
|
+
cardinality,
|
|
194
|
+
maxAffected,
|
|
195
|
+
expiresAt,
|
|
196
|
+
arguments: parseConstraint(ownValue(source, "arguments")),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/** Validate and normalize an untrusted startup template before hashing it. */
|
|
200
|
+
export async function compileExactPolicy(input) {
|
|
201
|
+
try {
|
|
202
|
+
// Both operations are synchronous: no caller-owned data survives to the
|
|
203
|
+
// first await, including through proxies, accessors, or later mutation.
|
|
204
|
+
const snapshotJson = canonicalJsonBounded(input, {
|
|
205
|
+
maxBytes: MAX_POLICY_BYTES,
|
|
206
|
+
maxDepth: MAX_POLICY_DEPTH,
|
|
207
|
+
maxNodes: MAX_POLICY_NODES,
|
|
208
|
+
});
|
|
209
|
+
const snapshot = parseJsonStrict(snapshotJson, {
|
|
210
|
+
maxBytes: MAX_POLICY_BYTES,
|
|
211
|
+
maxDepth: MAX_POLICY_DEPTH,
|
|
212
|
+
maxKeys: MAX_POLICY_NODES,
|
|
213
|
+
});
|
|
214
|
+
const template = parseTemplate(snapshot);
|
|
215
|
+
const policyDigest = await sha256(POLICY_DOMAIN, template);
|
|
216
|
+
return Object.freeze({ template, policyDigest });
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
throw invalid();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function ownJsonObject(value) {
|
|
223
|
+
try {
|
|
224
|
+
return objectWithExactKeys(value, null);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function isCanonicalArray(value) {
|
|
231
|
+
if (Object.getPrototypeOf(value) !== Array.prototype)
|
|
232
|
+
return false;
|
|
233
|
+
const keys = Reflect.ownKeys(value);
|
|
234
|
+
if (keys.length !== value.length + 1)
|
|
235
|
+
return false;
|
|
236
|
+
const lengthDescriptor = Object.getOwnPropertyDescriptor(value, "length");
|
|
237
|
+
if (lengthDescriptor === undefined ||
|
|
238
|
+
!("value" in lengthDescriptor) ||
|
|
239
|
+
lengthDescriptor.enumerable ||
|
|
240
|
+
lengthDescriptor.value !== value.length) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
244
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
245
|
+
if (descriptor === undefined ||
|
|
246
|
+
!("value" in descriptor) ||
|
|
247
|
+
!descriptor.enumerable) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
function exactJson(left, right) {
|
|
254
|
+
if (left === null ||
|
|
255
|
+
typeof left === "boolean" ||
|
|
256
|
+
typeof left === "number" ||
|
|
257
|
+
typeof left === "string") {
|
|
258
|
+
return Object.is(left, right) || (left === 0 && right === 0);
|
|
259
|
+
}
|
|
260
|
+
if (Array.isArray(left)) {
|
|
261
|
+
if (!Array.isArray(right) ||
|
|
262
|
+
right.length !== left.length ||
|
|
263
|
+
!isCanonicalArray(right)) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
267
|
+
const descriptor = Object.getOwnPropertyDescriptor(right, String(index));
|
|
268
|
+
if (descriptor === undefined ||
|
|
269
|
+
!("value" in descriptor) ||
|
|
270
|
+
!exactJson(left[index], descriptor.value)) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return true;
|
|
275
|
+
}
|
|
276
|
+
const rightObject = ownJsonObject(right);
|
|
277
|
+
if (rightObject === null)
|
|
278
|
+
return false;
|
|
279
|
+
const keys = Object.keys(left).sort();
|
|
280
|
+
const rightKeys = Object.keys(rightObject).sort();
|
|
281
|
+
if (keys.length !== rightKeys.length ||
|
|
282
|
+
keys.some((key, index) => key !== rightKeys[index])) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
return keys.every((key) => exactJson(left[key], ownValue(rightObject, key)));
|
|
286
|
+
}
|
|
287
|
+
function matchesConstraint(constraint, value) {
|
|
288
|
+
if (constraint.kind === "exact") {
|
|
289
|
+
return exactJson(constraint.value, value);
|
|
290
|
+
}
|
|
291
|
+
if (constraint.kind === "string-set") {
|
|
292
|
+
return typeof value === "string" && constraint.values.includes(value);
|
|
293
|
+
}
|
|
294
|
+
if (constraint.kind === "number") {
|
|
295
|
+
return (typeof value === "number" &&
|
|
296
|
+
Number.isFinite(value) &&
|
|
297
|
+
value >= constraint.min &&
|
|
298
|
+
value <= constraint.max);
|
|
299
|
+
}
|
|
300
|
+
if (constraint.kind === "array") {
|
|
301
|
+
if (!Array.isArray(value) ||
|
|
302
|
+
value.length > constraint.maxItems ||
|
|
303
|
+
!isCanonicalArray(value)) {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
307
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
308
|
+
if (descriptor === undefined ||
|
|
309
|
+
!("value" in descriptor) ||
|
|
310
|
+
!matchesConstraint(constraint.items, descriptor.value)) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
const object = ownJsonObject(value);
|
|
317
|
+
if (object === null)
|
|
318
|
+
return false;
|
|
319
|
+
const expectedKeys = Object.keys(constraint.properties).sort();
|
|
320
|
+
const actualKeys = Object.keys(object).sort();
|
|
321
|
+
if (expectedKeys.length !== actualKeys.length ||
|
|
322
|
+
expectedKeys.some((key, index) => key !== actualKeys[index])) {
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
return expectedKeys.every((key) => matchesConstraint(constraint.properties[key], ownValue(object, key)));
|
|
326
|
+
}
|
|
327
|
+
/** Return true only when every policy identity and closed constraint matches. */
|
|
328
|
+
export function matchesExactPolicy(policy, call, nowMs) {
|
|
329
|
+
try {
|
|
330
|
+
const template = policy.template;
|
|
331
|
+
if (!Number.isSafeInteger(nowMs) ||
|
|
332
|
+
nowMs >= template.expiresAt ||
|
|
333
|
+
call.safety !== "action" ||
|
|
334
|
+
(call.actionKind !== "create" && call.actionKind !== "update") ||
|
|
335
|
+
call.actionKind !== template.actionKind ||
|
|
336
|
+
call.catalogId !== template.catalogId ||
|
|
337
|
+
call.releaseId !== template.releaseId ||
|
|
338
|
+
call.manifestDigest !== template.manifestDigest ||
|
|
339
|
+
call.operationId !== template.operationId ||
|
|
340
|
+
call.operationDigest !== template.operationDigest ||
|
|
341
|
+
call.credentialProfileDigest !== template.credentialProfileDigest) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
if (template.cardinality === "single") {
|
|
345
|
+
if (call.cardinality?.kind !== "single" || template.maxAffected !== 1) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
else if (call.cardinality?.kind !== "bounded" ||
|
|
350
|
+
!Number.isSafeInteger(call.cardinality.maxAffected) ||
|
|
351
|
+
call.cardinality.maxAffected > MAX_AFFECTED ||
|
|
352
|
+
call.cardinality.maxAffected > template.maxAffected ||
|
|
353
|
+
call.cardinality.maxAffected < 1) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
return matchesConstraint(template.arguments, call.normalizedArguments);
|
|
357
|
+
}
|
|
358
|
+
catch {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type StdioServerHandle } from "@modelcontextprotocol/server/stdio";
|
|
2
|
+
import { type OpenApiStdioConfig } from "./config.ts";
|
|
3
|
+
export { type OpenApiStdioConfig, parseOpenApiStdioConfig } from "./config.ts";
|
|
4
|
+
export { createOpenApiMcpServer, type OpenApiMcpServerOptions, } from "./server.ts";
|
|
5
|
+
/** Owns process-local credentials/catalogs independently of disposable SDK probes. */
|
|
6
|
+
export declare function serveOpenApiStdio(input: OpenApiStdioConfig): Promise<StdioServerHandle>;
|