@treeseed/sdk 0.13.0-rc.3 → 0.13.0-rc.6

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.
Files changed (30) hide show
  1. package/dist/entrypoints/clients/control-plane-client.d.ts +62 -0
  2. package/dist/entrypoints/clients/control-plane-client.js +96 -0
  3. package/dist/market-client/interface.d.ts +0 -5
  4. package/dist/market-client/internal-capacity-allocation.d.ts +4 -0
  5. package/dist/market-client/internal-capacity-allocation.js +17 -0
  6. package/dist/market-client/methods.js +0 -10
  7. package/dist/operator-contracts/control-plane-operation.d.ts +60 -0
  8. package/dist/operator-contracts/control-plane-operation.js +75 -0
  9. package/dist/operator-contracts/index.d.ts +3 -0
  10. package/dist/operator-contracts/index.js +3 -0
  11. package/dist/operator-contracts/mcp.d.ts +78 -0
  12. package/dist/operator-contracts/mcp.js +23 -0
  13. package/dist/operator-contracts/oauth.d.ts +38 -0
  14. package/dist/operator-contracts/oauth.js +0 -0
  15. package/dist/operator-contracts/workday-profile.d.ts +6 -0
  16. package/dist/operator-contracts/workday-profile.js +17 -0
  17. package/dist/reconcile/capacity/capacity-core/live-acceptance-capacity-competition.js +3 -2
  18. package/dist/reconcile/capacity/capacity-core/live-acceptance-capacity-proof.js +4 -2
  19. package/dist/reconcile/runtime/live-acceptance-starter-runtime.js +5 -4
  20. package/dist/scenes/agent-lab/production-lifecycle.js +3 -2
  21. package/dist/seeds/runtime/local-capacity.js +4 -2
  22. package/dist/standards/mcp/compare.d.ts +2 -0
  23. package/dist/standards/mcp/compare.js +38 -0
  24. package/dist/standards/mcp/contracts.d.ts +34 -0
  25. package/dist/standards/mcp/contracts.js +0 -0
  26. package/dist/standards/mcp/index.d.ts +3 -0
  27. package/dist/standards/mcp/index.js +3 -0
  28. package/dist/standards/mcp/normalize.d.ts +4 -0
  29. package/dist/standards/mcp/normalize.js +30 -0
  30. package/package.json +9 -1
@@ -0,0 +1,62 @@
1
+ import type { ApiPrincipal } from './remote.js';
2
+ export declare const DEFAULT_CONTROL_PLANE_BASE_URL = "http://127.0.0.1:3002";
3
+ export declare const CONTROL_PLANE_BASE_URL_ENV = "TREESEED_API_BASE_URL";
4
+ export interface ControlPlaneServerProfile {
5
+ serverId: string;
6
+ label: string;
7
+ baseUrl: string;
8
+ }
9
+ export interface ControlPlaneServerSession {
10
+ serverId: string;
11
+ accessToken: string;
12
+ refreshToken?: string;
13
+ expiresAt?: string;
14
+ principal?: ApiPrincipal | null;
15
+ }
16
+ export interface ControlPlaneClientOptions {
17
+ profile: ControlPlaneServerProfile;
18
+ accessToken?: string | null;
19
+ fetchImpl?: typeof fetch;
20
+ userAgent?: string;
21
+ }
22
+ export interface ControlPlaneResponseEnvelope<T> {
23
+ data: T;
24
+ meta?: Record<string, unknown>;
25
+ links?: Record<string, string>;
26
+ }
27
+ export interface ProblemDetails {
28
+ type: string;
29
+ title: string;
30
+ status: number;
31
+ detail?: string;
32
+ instance?: string;
33
+ code: string;
34
+ requestId?: string;
35
+ traceId?: string;
36
+ fields?: Record<string, string[]>;
37
+ }
38
+ export interface ControlPlaneCallOptions {
39
+ method?: 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
40
+ path: `/v1/${string}` | '/openapi.json';
41
+ input?: unknown;
42
+ headers?: Record<string, string>;
43
+ idempotencyKey?: string;
44
+ ifMatch?: string;
45
+ signal?: AbortSignal;
46
+ }
47
+ export declare class ControlPlaneClientError extends Error {
48
+ readonly status: number;
49
+ readonly problem: ProblemDetails;
50
+ readonly responseHeaders: Headers;
51
+ constructor(message: string, status: number, problem: ProblemDetails, responseHeaders: Headers);
52
+ }
53
+ export declare function defaultLocalControlPlaneServer(env?: Record<string, string | undefined>): ControlPlaneServerProfile;
54
+ export declare class ControlPlaneClient {
55
+ readonly options: ControlPlaneClientOptions;
56
+ readonly baseUrl: string;
57
+ readonly accessToken: string | null;
58
+ readonly fetchImpl: typeof fetch;
59
+ readonly userAgent?: string;
60
+ constructor(options: ControlPlaneClientOptions);
61
+ call<T>(options: ControlPlaneCallOptions): Promise<ControlPlaneResponseEnvelope<T>>;
62
+ }
@@ -0,0 +1,96 @@
1
+ const DEFAULT_CONTROL_PLANE_BASE_URL = "http://127.0.0.1:3002";
2
+ const CONTROL_PLANE_BASE_URL_ENV = "TREESEED_API_BASE_URL";
3
+ class ControlPlaneClientError extends Error {
4
+ constructor(message, status, problem, responseHeaders) {
5
+ super(message);
6
+ this.status = status;
7
+ this.problem = problem;
8
+ this.responseHeaders = responseHeaders;
9
+ this.name = "ControlPlaneClientError";
10
+ }
11
+ status;
12
+ problem;
13
+ responseHeaders;
14
+ }
15
+ function normalizeBaseUrl(value) {
16
+ const normalized = value.trim().replace(/\/+$/u, "");
17
+ if (!/^https?:\/\//u.test(normalized)) throw new Error("Control-plane server URLs must use HTTP or HTTPS.");
18
+ return normalized;
19
+ }
20
+ function defaultLocalControlPlaneServer(env = process.env) {
21
+ return {
22
+ serverId: "local",
23
+ label: "Local TreeSeed control plane",
24
+ baseUrl: normalizeBaseUrl(env[CONTROL_PLANE_BASE_URL_ENV] ?? DEFAULT_CONTROL_PLANE_BASE_URL)
25
+ };
26
+ }
27
+ async function responsePayload(response) {
28
+ const contentType = response.headers.get("content-type") ?? "";
29
+ if (contentType.includes("json")) return response.json();
30
+ const text = await response.text();
31
+ return text.length > 0 ? text : null;
32
+ }
33
+ function problemFrom(payload, status) {
34
+ const source = payload && typeof payload === "object" ? payload : {};
35
+ return {
36
+ type: typeof source.type === "string" ? source.type : "about:blank",
37
+ title: typeof source.title === "string" ? source.title : "Control-plane request failed",
38
+ status,
39
+ detail: typeof source.detail === "string" ? source.detail : void 0,
40
+ instance: typeof source.instance === "string" ? source.instance : void 0,
41
+ code: typeof source.code === "string" ? source.code : "control_plane_request_failed",
42
+ requestId: typeof source.requestId === "string" ? source.requestId : void 0,
43
+ traceId: typeof source.traceId === "string" ? source.traceId : void 0,
44
+ fields: source.fields && typeof source.fields === "object" ? source.fields : void 0
45
+ };
46
+ }
47
+ class ControlPlaneClient {
48
+ constructor(options) {
49
+ this.options = options;
50
+ this.baseUrl = normalizeBaseUrl(options.profile.baseUrl);
51
+ this.accessToken = options.accessToken ?? null;
52
+ this.fetchImpl = options.fetchImpl ?? fetch;
53
+ this.userAgent = options.userAgent;
54
+ }
55
+ options;
56
+ baseUrl;
57
+ accessToken;
58
+ fetchImpl;
59
+ userAgent;
60
+ async call(options) {
61
+ const headers = new Headers(options.headers);
62
+ headers.set("accept", "application/json, application/problem+json");
63
+ if (this.accessToken) headers.set("authorization", `Bearer ${this.accessToken}`);
64
+ if (this.userAgent) headers.set("user-agent", this.userAgent);
65
+ if (options.idempotencyKey) headers.set("idempotency-key", options.idempotencyKey);
66
+ if (options.ifMatch) headers.set("if-match", options.ifMatch);
67
+ if (options.input !== void 0) headers.set("content-type", "application/json");
68
+ const response = await this.fetchImpl(`${this.baseUrl}${options.path}`, {
69
+ method: options.method ?? "GET",
70
+ headers,
71
+ body: options.input === void 0 ? void 0 : JSON.stringify(options.input),
72
+ signal: options.signal
73
+ });
74
+ const payload = await responsePayload(response);
75
+ if (!response.ok) {
76
+ const problem = problemFrom(payload, response.status);
77
+ throw new ControlPlaneClientError(problem.detail ?? problem.title, response.status, problem, response.headers);
78
+ }
79
+ if (!payload || typeof payload !== "object" || !("data" in payload)) {
80
+ throw new ControlPlaneClientError("The control plane returned an invalid success envelope.", 502, {
81
+ type: "https://treeseed.dev/problems/invalid-upstream-response",
82
+ title: "Invalid control-plane response",
83
+ status: 502,
84
+ code: "control_plane_response_invalid"
85
+ }, response.headers);
86
+ }
87
+ return payload;
88
+ }
89
+ }
90
+ export {
91
+ CONTROL_PLANE_BASE_URL_ENV,
92
+ ControlPlaneClient,
93
+ ControlPlaneClientError,
94
+ DEFAULT_CONTROL_PLANE_BASE_URL,
95
+ defaultLocalControlPlaneServer
96
+ };
@@ -119,12 +119,7 @@ declare module "../support/market-client.js" {
119
119
  createCapacityGrant: OmitThisParameter<typeof import("./capacity/allocations/creation/create-capacity-grant.js").createCapacityGrantMethod>;
120
120
  transitionCapacityGrant: OmitThisParameter<typeof import("./capacity/allocations/updates/transition-capacity-grant.js").transitionCapacityGrantMethod>;
121
121
  capacityAllocationSets: OmitThisParameter<typeof import("./capacity/allocations/contracts/capacity-allocation-sets.js").capacityAllocationSetsMethod>;
122
- createCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/creation/create-capacity-allocation-set.js").createCapacityAllocationSetMethod>;
123
- planCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/creation/plan-capacity-allocation-set.js").planCapacityAllocationSetMethod>;
124
122
  capacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/contracts/capacity-allocation-set.js").capacityAllocationSetMethod>;
125
- activateCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/lifecycle/activate-capacity-allocation-set.js").activateCapacityAllocationSetMethod>;
126
- supersedeCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/updates/supersede-capacity-allocation-set.js").supersedeCapacityAllocationSetMethod>;
127
- archiveCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/retirement/archive-capacity-allocation-set.js").archiveCapacityAllocationSetMethod>;
128
123
  explainCapacityAllocationSet: OmitThisParameter<typeof import("./capacity/allocations/queries/explain-capacity-allocation-set.js").explainCapacityAllocationSetMethod>;
129
124
  providerAvailabilitySessions: OmitThisParameter<typeof import("./capacity/providers/contracts/provider-availability-sessions.js").providerAvailabilitySessionsMethod>;
130
125
  capacityProviderAssignments: OmitThisParameter<typeof import("./capacity/assignments/contracts/capacity-provider-assignments.js").capacityProviderAssignmentsMethod>;
@@ -0,0 +1,4 @@
1
+ import type { MarketClient } from '../entrypoints/clients/market-client.js';
2
+ export declare function createInternalCapacityAllocationSet(client: MarketClient, teamId: string, body: Record<string, unknown>, idempotencyKey: string): any;
3
+ export declare function activateInternalCapacityAllocationSet(client: MarketClient, teamId: string, allocationSetId: string, idempotencyKey: string): any;
4
+ export declare function supersedeInternalCapacityAllocationSet(client: MarketClient, teamId: string, allocationSetId: string, body: Record<string, unknown>, idempotencyKey: string): any;
@@ -0,0 +1,17 @@
1
+ import { createCapacityAllocationSetMethod } from "./capacity/allocations/creation/create-capacity-allocation-set.js";
2
+ import { activateCapacityAllocationSetMethod } from "./capacity/allocations/lifecycle/activate-capacity-allocation-set.js";
3
+ import { supersedeCapacityAllocationSetMethod } from "./capacity/allocations/updates/supersede-capacity-allocation-set.js";
4
+ function createInternalCapacityAllocationSet(client, teamId, body, idempotencyKey) {
5
+ return createCapacityAllocationSetMethod.call(client, teamId, body, idempotencyKey);
6
+ }
7
+ function activateInternalCapacityAllocationSet(client, teamId, allocationSetId, idempotencyKey) {
8
+ return activateCapacityAllocationSetMethod.call(client, teamId, allocationSetId, idempotencyKey);
9
+ }
10
+ function supersedeInternalCapacityAllocationSet(client, teamId, allocationSetId, body, idempotencyKey) {
11
+ return supersedeCapacityAllocationSetMethod.call(client, teamId, allocationSetId, body, idempotencyKey);
12
+ }
13
+ export {
14
+ activateInternalCapacityAllocationSet,
15
+ createInternalCapacityAllocationSet,
16
+ supersedeInternalCapacityAllocationSet
17
+ };
@@ -47,14 +47,9 @@ import { capacityAllocationSetMethod } from "./capacity/allocations/contracts/ca
47
47
  import { capacityAllocationSetsMethod } from "./capacity/allocations/contracts/capacity-allocation-sets.js";
48
48
  import { capacityGrantMethod } from "./capacity/allocations/contracts/capacity-grant.js";
49
49
  import { capacityGrantsMethod } from "./capacity/allocations/contracts/capacity-grants.js";
50
- import { createCapacityAllocationSetMethod } from "./capacity/allocations/creation/create-capacity-allocation-set.js";
51
50
  import { createCapacityGrantMethod } from "./capacity/allocations/creation/create-capacity-grant.js";
52
- import { planCapacityAllocationSetMethod } from "./capacity/allocations/creation/plan-capacity-allocation-set.js";
53
51
  import { planCapacityGrantMethod } from "./capacity/allocations/creation/plan-capacity-grant.js";
54
- import { activateCapacityAllocationSetMethod } from "./capacity/allocations/lifecycle/activate-capacity-allocation-set.js";
55
52
  import { explainCapacityAllocationSetMethod } from "./capacity/allocations/queries/explain-capacity-allocation-set.js";
56
- import { archiveCapacityAllocationSetMethod } from "./capacity/allocations/retirement/archive-capacity-allocation-set.js";
57
- import { supersedeCapacityAllocationSetMethod } from "./capacity/allocations/updates/supersede-capacity-allocation-set.js";
58
53
  import { transitionCapacityGrantMethod } from "./capacity/allocations/updates/transition-capacity-grant.js";
59
54
  import { capacityProviderAssignmentMethod } from "./capacity/assignments/contracts/capacity-provider-assignment.js";
60
55
  import { assignmentAuthorityProbeMethod } from "./capacity/assignments/contracts/assignment-authority-probe.js";
@@ -303,12 +298,7 @@ function installMarketClientMethods(prototype) {
303
298
  prototype.createCapacityGrant = createCapacityGrantMethod;
304
299
  prototype.transitionCapacityGrant = transitionCapacityGrantMethod;
305
300
  prototype.capacityAllocationSets = capacityAllocationSetsMethod;
306
- prototype.createCapacityAllocationSet = createCapacityAllocationSetMethod;
307
- prototype.planCapacityAllocationSet = planCapacityAllocationSetMethod;
308
301
  prototype.capacityAllocationSet = capacityAllocationSetMethod;
309
- prototype.activateCapacityAllocationSet = activateCapacityAllocationSetMethod;
310
- prototype.supersedeCapacityAllocationSet = supersedeCapacityAllocationSetMethod;
311
- prototype.archiveCapacityAllocationSet = archiveCapacityAllocationSetMethod;
312
302
  prototype.explainCapacityAllocationSet = explainCapacityAllocationSetMethod;
313
303
  prototype.providerAvailabilitySessions = providerAvailabilitySessionsMethod;
314
304
  prototype.capacityProviderAssignments = capacityProviderAssignmentsMethod;
@@ -0,0 +1,60 @@
1
+ export declare const CONTROL_PLANE_OPERATION_SCHEMA_VERSION: "treeseed.control-plane-operation/v1";
2
+ export type ControlPlaneHttpMethod = 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';
3
+ export type ControlPlaneOperationKind = 'read' | 'mutation';
4
+ export type ControlPlaneRiskClass = 'ordinary' | 'destructive' | 'credential' | 'authority' | 'production' | 'irreversible';
5
+ export type ControlPlaneConfirmationPolicy = 'never' | 'input_required';
6
+ export type ControlPlaneOperationSurface = 'rest' | 'cli' | 'mcp_tool' | 'mcp_resource' | 'internal';
7
+ export type ControlPlaneCacheScope = 'none' | 'principal' | 'team' | 'project' | 'public';
8
+ export type ControlPlanePaginationKind = 'none' | 'cursor';
9
+ export interface ControlPlaneRestBinding {
10
+ method: ControlPlaneHttpMethod;
11
+ path: `/v1/${string}`;
12
+ }
13
+ export interface ControlPlaneSchemaBinding {
14
+ input: string;
15
+ output: string;
16
+ parameters?: string;
17
+ errors: string;
18
+ }
19
+ export interface ControlPlaneIdempotencyContract {
20
+ required: boolean;
21
+ header: 'Idempotency-Key';
22
+ }
23
+ export interface ControlPlaneConcurrencyContract {
24
+ required: boolean;
25
+ readHeader: 'ETag';
26
+ writeHeader: 'If-Match';
27
+ }
28
+ export interface ControlPlaneOperationDescriptor {
29
+ schemaVersion: typeof CONTROL_PLANE_OPERATION_SCHEMA_VERSION;
30
+ operationId: `${string}.${string}`;
31
+ description: string;
32
+ rest?: ControlPlaneRestBinding;
33
+ schemas: ControlPlaneSchemaBinding;
34
+ capability: string;
35
+ oauthScopes: OAuthScope[];
36
+ kind: ControlPlaneOperationKind;
37
+ riskClass: ControlPlaneRiskClass;
38
+ confirmation: ControlPlaneConfirmationPolicy;
39
+ idempotency: ControlPlaneIdempotencyContract;
40
+ concurrency: ControlPlaneConcurrencyContract;
41
+ surfaces: ControlPlaneOperationSurface[];
42
+ cacheScope: ControlPlaneCacheScope;
43
+ pagination: ControlPlanePaginationKind;
44
+ audited: boolean;
45
+ receipt: boolean;
46
+ redactedPaths: string[];
47
+ }
48
+ export declare const TREESEED_OAUTH_SCOPES: readonly ["treeseed:read", "treeseed:knowledge:write", "treeseed:governance:write", "treeseed:projects:write", "treeseed:execution", "treeseed:admin"];
49
+ export type OAuthScope = typeof TREESEED_OAUTH_SCOPES[number];
50
+ export interface ControlPlaneCatalog {
51
+ schemaVersion: 'treeseed.control-plane-catalog/v1';
52
+ operations: ControlPlaneOperationDescriptor[];
53
+ }
54
+ export interface ControlPlaneCatalogDiagnostic {
55
+ code: string;
56
+ path: string;
57
+ message: string;
58
+ }
59
+ export declare function validateControlPlaneCatalog(catalog: ControlPlaneCatalog): ControlPlaneCatalogDiagnostic[];
60
+ export declare function indexControlPlaneCatalog(catalog: ControlPlaneCatalog): Map<`${string}.${string}`, ControlPlaneOperationDescriptor>;
@@ -0,0 +1,75 @@
1
+ const CONTROL_PLANE_OPERATION_SCHEMA_VERSION = "treeseed.control-plane-operation/v1";
2
+ const TREESEED_OAUTH_SCOPES = [
3
+ "treeseed:read",
4
+ "treeseed:knowledge:write",
5
+ "treeseed:governance:write",
6
+ "treeseed:projects:write",
7
+ "treeseed:execution",
8
+ "treeseed:admin"
9
+ ];
10
+ const OPERATION_ID = /^[a-z][a-z0-9]*(?:\.[a-z][a-z0-9]*)+$/u;
11
+ const PATH_PARAMETER = /\{([A-Za-z][A-Za-z0-9]*)\}/gu;
12
+ function duplicates(values) {
13
+ const seen = /* @__PURE__ */ new Set();
14
+ return values.filter((value) => seen.size === seen.add(value).size);
15
+ }
16
+ function validateControlPlaneCatalog(catalog) {
17
+ const diagnostics = [];
18
+ const operationIds = /* @__PURE__ */ new Set();
19
+ const restBindings = /* @__PURE__ */ new Set();
20
+ for (const [index, operation] of catalog.operations.entries()) {
21
+ const path = `operations.${index}`;
22
+ if (operation.schemaVersion !== CONTROL_PLANE_OPERATION_SCHEMA_VERSION) {
23
+ diagnostics.push({ code: "operation_schema_version_invalid", path: `${path}.schemaVersion`, message: "Operation schemaVersion is not supported." });
24
+ }
25
+ if (!OPERATION_ID.test(operation.operationId)) {
26
+ diagnostics.push({ code: "operation_id_invalid", path: `${path}.operationId`, message: "Operation IDs must be stable dotted lowercase words." });
27
+ }
28
+ if (operationIds.has(operation.operationId)) {
29
+ diagnostics.push({ code: "operation_id_duplicate", path: `${path}.operationId`, message: `Duplicate operation ID ${operation.operationId}.` });
30
+ }
31
+ operationIds.add(operation.operationId);
32
+ if (operation.surfaces.includes("rest") !== Boolean(operation.rest)) {
33
+ diagnostics.push({ code: "rest_binding_mismatch", path: `${path}.rest`, message: "REST surface and REST binding must be declared together." });
34
+ }
35
+ if (operation.rest) {
36
+ const binding = `${operation.rest.method} ${operation.rest.path}`;
37
+ if (restBindings.has(binding)) diagnostics.push({ code: "rest_binding_duplicate", path: `${path}.rest`, message: `Duplicate REST binding ${binding}.` });
38
+ restBindings.add(binding);
39
+ const parameters = [...operation.rest.path.matchAll(PATH_PARAMETER)].map((match) => match[1]);
40
+ if (parameters.length > 0 && !operation.schemas.parameters) {
41
+ diagnostics.push({ code: "parameter_schema_required", path: `${path}.schemas.parameters`, message: "Parameterized REST paths require a parameter schema." });
42
+ }
43
+ }
44
+ for (const scope of operation.oauthScopes) {
45
+ if (!TREESEED_OAUTH_SCOPES.includes(scope)) diagnostics.push({ code: "oauth_scope_invalid", path: `${path}.oauthScopes`, message: `Unknown OAuth scope ${scope}.` });
46
+ }
47
+ for (const duplicate of duplicates(operation.surfaces)) diagnostics.push({ code: "surface_duplicate", path: `${path}.surfaces`, message: `Duplicate operation surface ${duplicate}.` });
48
+ for (const duplicate of duplicates(operation.oauthScopes)) diagnostics.push({ code: "oauth_scope_duplicate", path: `${path}.oauthScopes`, message: `Duplicate OAuth scope ${duplicate}.` });
49
+ const elevatedRisk = operation.riskClass !== "ordinary";
50
+ if (elevatedRisk !== (operation.confirmation === "input_required")) {
51
+ diagnostics.push({ code: "confirmation_policy_invalid", path: `${path}.confirmation`, message: "Elevated-risk operations require input_required; ordinary operations must not." });
52
+ }
53
+ if (operation.kind === "read" && (operation.idempotency.required || operation.concurrency.required || operation.receipt)) {
54
+ diagnostics.push({ code: "read_mutation_contract_invalid", path, message: "Read operations cannot require mutation idempotency, write concurrency, or mutation receipts." });
55
+ }
56
+ if (operation.kind === "mutation" && operation.surfaces.some((surface) => surface !== "internal") && !operation.audited) {
57
+ diagnostics.push({ code: "mutation_audit_required", path: `${path}.audited`, message: "Every externally reachable mutation must be audited." });
58
+ }
59
+ if (operation.kind === "mutation" && !operation.receipt) {
60
+ diagnostics.push({ code: "mutation_receipt_required", path: `${path}.receipt`, message: "Mutations must return durable receipts." });
61
+ }
62
+ }
63
+ return diagnostics.sort((left, right) => `${left.path}:${left.code}`.localeCompare(`${right.path}:${right.code}`));
64
+ }
65
+ function indexControlPlaneCatalog(catalog) {
66
+ const diagnostics = validateControlPlaneCatalog(catalog);
67
+ if (diagnostics.length > 0) throw new Error(`Invalid control-plane catalog: ${diagnostics.map((entry) => entry.code).join(", ")}`);
68
+ return new Map(catalog.operations.map((operation) => [operation.operationId, operation]));
69
+ }
70
+ export {
71
+ CONTROL_PLANE_OPERATION_SCHEMA_VERSION,
72
+ TREESEED_OAUTH_SCOPES,
73
+ indexControlPlaneCatalog,
74
+ validateControlPlaneCatalog
75
+ };
@@ -2,3 +2,6 @@ export * from './command-tree.js';
2
2
  export * from './canonical-command-tree.js';
3
3
  export * from './workday-profile.js';
4
4
  export * from './workday-lifecycle.js';
5
+ export * from './control-plane-operation.js';
6
+ export * from './mcp.js';
7
+ export * from './oauth.js';
@@ -2,3 +2,6 @@ export * from "./command-tree.js";
2
2
  export * from "./canonical-command-tree.js";
3
3
  export * from "./workday-profile.js";
4
4
  export * from "./workday-lifecycle.js";
5
+ export * from "./control-plane-operation.js";
6
+ export * from "./mcp.js";
7
+ export * from "./oauth.js";
@@ -0,0 +1,78 @@
1
+ import type { ControlPlaneOperationDescriptor, OAuthScope } from './control-plane-operation.js';
2
+ export declare const MCP_PROTOCOL_VERSION: "2026-07-28";
3
+ export interface ResourceLink {
4
+ type: 'resource_link';
5
+ uri: `treeseed://${string}`;
6
+ name: string;
7
+ title?: string;
8
+ description?: string;
9
+ mimeType?: string;
10
+ }
11
+ export interface ActorChain {
12
+ principalId: string;
13
+ delegatedAgentId?: string;
14
+ oauthClientId: string;
15
+ interface: 'rest' | 'cli' | 'mcp' | 'site_bff' | 'internal';
16
+ conversationId?: string;
17
+ modelClaim?: string;
18
+ skillClaim?: string;
19
+ traceId: string;
20
+ }
21
+ export interface ConfirmationState {
22
+ schemaVersion: 'treeseed.confirmation-state/v1';
23
+ principalId: string;
24
+ clientId: string;
25
+ operationId: string;
26
+ argumentsDigest: `sha256:${string}`;
27
+ expiresAt: string;
28
+ nonce: string;
29
+ signature: string;
30
+ }
31
+ export interface InputRequired {
32
+ type: 'input_required';
33
+ requestId: string;
34
+ prompt: string;
35
+ confirmation: ConfirmationState;
36
+ }
37
+ export interface McpToolDescriptor {
38
+ name: string;
39
+ description: string;
40
+ inputSchemaId: string;
41
+ outputSchemaId: string;
42
+ operationId: string;
43
+ readOnlyHint: boolean;
44
+ destructiveHint: boolean;
45
+ idempotentHint: boolean;
46
+ openWorldHint: boolean;
47
+ }
48
+ export interface McpResourceDescriptor {
49
+ uriTemplate: `treeseed://${string}`;
50
+ name: string;
51
+ description: string;
52
+ mimeType: string;
53
+ operationId: string;
54
+ subscribable: boolean;
55
+ cacheTtlSeconds?: number;
56
+ }
57
+ export interface McpPromptDescriptor {
58
+ name: string;
59
+ description: string;
60
+ argumentSchemaId: string;
61
+ requiredScopes: OAuthScope[];
62
+ }
63
+ export interface McpCatalog {
64
+ schemaVersion: 'treeseed.mcp-catalog/v1';
65
+ protocolVersion: typeof MCP_PROTOCOL_VERSION;
66
+ tools: McpToolDescriptor[];
67
+ resources: McpResourceDescriptor[];
68
+ prompts: McpPromptDescriptor[];
69
+ capabilities: {
70
+ completion: true;
71
+ progress: true;
72
+ cancellation: true;
73
+ inputRequired: true;
74
+ resourceSubscriptions: true;
75
+ };
76
+ }
77
+ export declare function operationToMcpTool(operation: ControlPlaneOperationDescriptor): McpToolDescriptor | null;
78
+ export declare function buildMcpTools(operations: readonly ControlPlaneOperationDescriptor[]): McpToolDescriptor[];
@@ -0,0 +1,23 @@
1
+ const MCP_PROTOCOL_VERSION = "2026-07-28";
2
+ function operationToMcpTool(operation) {
3
+ if (!operation.surfaces.includes("mcp_tool")) return null;
4
+ return {
5
+ name: operation.operationId,
6
+ description: operation.description,
7
+ inputSchemaId: operation.schemas.input,
8
+ outputSchemaId: operation.schemas.output,
9
+ operationId: operation.operationId,
10
+ readOnlyHint: operation.kind === "read",
11
+ destructiveHint: operation.riskClass !== "ordinary",
12
+ idempotentHint: operation.kind === "read" || operation.idempotency.required,
13
+ openWorldHint: false
14
+ };
15
+ }
16
+ function buildMcpTools(operations) {
17
+ return operations.map(operationToMcpTool).filter((tool) => tool !== null).sort((left, right) => left.name.localeCompare(right.name));
18
+ }
19
+ export {
20
+ MCP_PROTOCOL_VERSION,
21
+ buildMcpTools,
22
+ operationToMcpTool
23
+ };
@@ -0,0 +1,38 @@
1
+ import type { OAuthScope } from './control-plane-operation.js';
2
+ export interface OAuthAuthorizationServerMetadata {
3
+ issuer: string;
4
+ authorization_endpoint: string;
5
+ token_endpoint: string;
6
+ device_authorization_endpoint: string;
7
+ revocation_endpoint: string;
8
+ response_types_supported: ['code'];
9
+ grant_types_supported: ['authorization_code', 'refresh_token', 'urn:ietf:params:oauth:grant-type:device_code'];
10
+ code_challenge_methods_supported: ['S256'];
11
+ scopes_supported: OAuthScope[];
12
+ }
13
+ export interface OAuthProtectedResourceMetadata {
14
+ resource: string;
15
+ authorization_servers: string[];
16
+ scopes_supported: OAuthScope[];
17
+ bearer_methods_supported: ['header'];
18
+ }
19
+ export interface OAuthDeviceAuthorizationRequest {
20
+ clientId: string;
21
+ scope: OAuthScope[];
22
+ }
23
+ export interface OAuthDeviceAuthorizationResponse {
24
+ deviceCode: string;
25
+ userCode: string;
26
+ verificationUri: string;
27
+ verificationUriComplete: string;
28
+ expiresIn: number;
29
+ interval: number;
30
+ }
31
+ export interface OAuthTokenReceipt {
32
+ tokenType: 'Bearer';
33
+ accessToken: string;
34
+ expiresIn: number;
35
+ refreshToken?: string;
36
+ scope: OAuthScope[];
37
+ audience: string;
38
+ }
File without changes
@@ -24,6 +24,10 @@ export interface WorkdayAllocationProfile {
24
24
  starvationLimitSeconds: number;
25
25
  };
26
26
  }
27
+ export interface RepositoryWorkdayProfileBundle {
28
+ schemaVersion: 'treeseed.workday-allocation-profile-bundle/v1';
29
+ profiles: WorkdayAllocationProfile[];
30
+ }
27
31
  export interface ProjectAgentClassMembership {
28
32
  projectId: string;
29
33
  agentId: string;
@@ -63,4 +67,6 @@ export interface WorkdayBorrowingEvidence {
63
67
  export declare function validateOneClassPerProjectAgent(memberships: ProjectAgentClassMembership[], expectedAgents?: ProjectAgentIdentity[]): WorkdayProfileDiagnostic[];
64
68
  export declare function validateWorkdayAllocationProfile(profile: WorkdayAllocationProfile, classCatalogs?: ProjectClassCatalog[]): WorkdayProfileDiagnostic[];
65
69
  export declare function normalizeWorkdayAllocationProfile(profile: WorkdayAllocationProfile): WorkdayAllocationProfile;
70
+ export declare function validateRepositoryWorkdayProfileBundle(bundle: RepositoryWorkdayProfileBundle, classCatalogs?: ProjectClassCatalog[]): WorkdayProfileDiagnostic[];
71
+ export declare function normalizeRepositoryWorkdayProfileBundle(bundle: RepositoryWorkdayProfileBundle): RepositoryWorkdayProfileBundle;
66
72
  export declare function validateWorkdayBorrowingEvidence(evidence: WorkdayBorrowingEvidence): WorkdayProfileDiagnostic[];
@@ -71,6 +71,21 @@ function normalizeWorkdayAllocationProfile(profile) {
71
71
  demandSources: [...new Set(profile.demandSources)].sort()
72
72
  };
73
73
  }
74
+ function validateRepositoryWorkdayProfileBundle(bundle, classCatalogs) {
75
+ const diagnostics = [];
76
+ if (bundle.schemaVersion !== "treeseed.workday-allocation-profile-bundle/v1") diagnostics.push({ code: "bundle_schema_version_invalid", path: "schemaVersion", message: "Unsupported repository workday profile bundle schema." });
77
+ if (!Array.isArray(bundle.profiles) || bundle.profiles.length === 0) diagnostics.push({ code: "bundle_profiles_empty", path: "profiles", message: "A repository workday profile bundle must contain at least one profile." });
78
+ const profileIds = /* @__PURE__ */ new Set();
79
+ for (const [index, profile] of (Array.isArray(bundle.profiles) ? bundle.profiles : []).entries()) {
80
+ if (profileIds.has(profile.id)) diagnostics.push({ code: "bundle_profile_id_duplicate", path: `profiles.${index}.id`, message: `Stable profile id ${profile.id} is declared more than once; a repository bundle contains exactly one current generation per profile.` });
81
+ profileIds.add(profile.id);
82
+ diagnostics.push(...validateWorkdayAllocationProfile(profile, classCatalogs).map((diagnostic) => ({ ...diagnostic, path: `profiles.${index}.${diagnostic.path}` })));
83
+ }
84
+ return diagnostics;
85
+ }
86
+ function normalizeRepositoryWorkdayProfileBundle(bundle) {
87
+ return { schemaVersion: "treeseed.workday-allocation-profile-bundle/v1", profiles: [...bundle.profiles].map(normalizeWorkdayAllocationProfile).sort((left, right) => left.id.localeCompare(right.id) || left.version.localeCompare(right.version)) };
88
+ }
74
89
  function validateWorkdayBorrowingEvidence(evidence) {
75
90
  const diagnostics = [];
76
91
  if (!evidence.lendingPermitted) diagnostics.push({ code: "lending_not_permitted", path: "lendingPermitted", message: "The profile does not permit the lender to lend capacity." });
@@ -82,8 +97,10 @@ function validateWorkdayBorrowingEvidence(evidence) {
82
97
  return diagnostics;
83
98
  }
84
99
  export {
100
+ normalizeRepositoryWorkdayProfileBundle,
85
101
  normalizeWorkdayAllocationProfile,
86
102
  validateOneClassPerProjectAgent,
103
+ validateRepositoryWorkdayProfileBundle,
87
104
  validateWorkdayAllocationProfile,
88
105
  validateWorkdayBorrowingEvidence
89
106
  };
@@ -1,4 +1,5 @@
1
1
  import { ProviderProtocolClient } from "../../../capacity/providers/capacity-provider.js";
2
+ import { activateInternalCapacityAllocationSet, createInternalCapacityAllocationSet } from "../../../market-client/internal-capacity-allocation.js";
2
3
  function projectRecord(payload) {
3
4
  const project = payload.project ?? payload;
4
5
  if (!project.id) throw new Error("Capacity competition project creation omitted its project id.");
@@ -96,7 +97,7 @@ async function provisionLocalCapacityCompetition(input) {
96
97
  }, `${prefix}:grant-create`);
97
98
  grantCreated = true;
98
99
  await input.adminClient.transitionCapacityGrant(input.runtime.teamId, grantId, "activate", `${prefix}:grant-activate`);
99
- const allocation = await input.adminClient.createCapacityAllocationSet(input.runtime.teamId, {
100
+ const allocation = await createInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, {
100
101
  id: allocationId,
101
102
  effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
102
103
  effectiveUntil: new Date(Date.now() + 10 * 6e4).toISOString(),
@@ -105,7 +106,7 @@ async function provisionLocalCapacityCompetition(input) {
105
106
  borrowingRules: [],
106
107
  metadata: { liveAcceptance: true, runId: input.runId }
107
108
  }, `${prefix}:allocation-create`);
108
- const activeAllocation = await input.adminClient.activateCapacityAllocationSet(input.runtime.teamId, String(allocation.payload.id), `${prefix}:allocation-activate`);
109
+ const activeAllocation = await activateInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, String(allocation.payload.id), `${prefix}:allocation-activate`);
109
110
  availability = await providerClient.refreshAvailabilitySession(sessionId, {
110
111
  expectedSequence: availability.payload.sequence,
111
112
  environment: "local",
@@ -1,5 +1,6 @@
1
1
  import { ProviderProtocolClient } from "../../../capacity/providers/capacity-provider.js";
2
2
  import { MarketClient } from "../../../entrypoints/clients/market-client.js";
3
+ import { activateInternalCapacityAllocationSet, createInternalCapacityAllocationSet } from "../../../market-client/internal-capacity-allocation.js";
3
4
  import { runLocalAutonomousStarterAcceptances } from "../../support/acceptance/live-acceptance-starters.js";
4
5
  import { configuredLiveAcceptanceValue } from "../../support/acceptance/live-acceptance-values.js";
5
6
  import { cleanupCapacityAssignmentProof } from "./live-acceptance-capacity-cleanup.js";
@@ -179,7 +180,7 @@ async function runCapacityProviderAssignmentProof(input) {
179
180
  let activeAllocation = effectiveActiveAllocation(Array.isArray(allocations.payload.items) ? allocations.payload.items : []);
180
181
  if (!activeAllocation?.id && input.environment === "local" && cleanupProvisionedProvider) {
181
182
  const allocationId = `${input.prefix}-allocation`;
182
- const created = await adminClient.createCapacityAllocationSet(config.teamId, {
183
+ const created = await createInternalCapacityAllocationSet(adminClient, config.teamId, {
183
184
  id: allocationId,
184
185
  effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
185
186
  effectiveUntil: new Date(Date.now() + 10 * 6e4).toISOString(),
@@ -193,7 +194,8 @@ async function runCapacityProviderAssignmentProof(input) {
193
194
  borrowingRules: [],
194
195
  metadata
195
196
  }, `capacity-acceptance:${input.runId}:allocation-create`);
196
- activeAllocation = (await adminClient.activateCapacityAllocationSet(
197
+ activeAllocation = (await activateInternalCapacityAllocationSet(
198
+ adminClient,
197
199
  config.teamId,
198
200
  String(created.payload.id),
199
201
  `capacity-acceptance:${input.runId}:allocation-activate`
@@ -3,6 +3,7 @@ import {
3
3
  ProviderProtocolClient,
4
4
  signCapacityProviderProof
5
5
  } from "../../capacity/providers/capacity-provider.js";
6
+ import { createInternalCapacityAllocationSet, supersedeInternalCapacityAllocationSet } from "../../market-client/internal-capacity-allocation.js";
6
7
  import { bindLocalCapacityTreeDxRepository, syncLocalAcceptanceAgentClasses } from "../capacity/capacity-core/live-acceptance-capacity-context.js";
7
8
  const LIVE_PROVIDER_ASSIGNMENT_BUDGET_SECONDS = 600;
8
9
  const TERMINAL_WORKDAY_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "cancelled", "failed", "degraded"]);
@@ -192,7 +193,7 @@ async function provisionLocalStarterCapacity(input) {
192
193
  }, `${key}:grant-create`);
193
194
  grantCreated = true;
194
195
  await input.adminClient.transitionCapacityGrant(input.runtime.teamId, grantId, "activate", `${key}:grant-activate`);
195
- const allocation = await input.adminClient.createCapacityAllocationSet(input.runtime.teamId, {
196
+ const allocation = await createInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, {
196
197
  id: allocationId,
197
198
  effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
198
199
  effectiveUntil: new Date(Date.now() + (durationSeconds + 300) * 1e3).toISOString(),
@@ -202,7 +203,7 @@ async function provisionLocalStarterCapacity(input) {
202
203
  metadata: { liveAcceptance: true, runId: input.runId, starter: input.config.starter }
203
204
  }, `${key}:allocation-create`);
204
205
  const active = (await input.adminClient.capacityAllocationSets(input.runtime.teamId, { limit: 200 })).payload.items.find((entry) => entry && typeof entry === "object" && entry.status === "active");
205
- await input.adminClient.supersedeCapacityAllocationSet(input.runtime.teamId, String(allocation.payload.id), { expectedActiveAllocationSetId: active?.id ?? null }, `${key}:allocation-supersede`);
206
+ await supersedeInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, String(allocation.payload.id), { expectedActiveAllocationSetId: active?.id ?? null }, `${key}:allocation-supersede`);
206
207
  availability = await protocol.refreshAvailabilitySession(sessionId, {
207
208
  expectedSequence: availability.payload.sequence,
208
209
  environment: "local",
@@ -372,7 +373,7 @@ async function provisionLocalStarterPortfolioCapacity(input) {
372
373
  }
373
374
  const durationSeconds = Math.max(...input.configs.map(localStarterDurationSeconds));
374
375
  const allocationId = `${key}:allocation`;
375
- const allocation = await input.adminClient.createCapacityAllocationSet(input.runtime.teamId, {
376
+ const allocation = await createInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, {
376
377
  id: allocationId,
377
378
  effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
378
379
  effectiveUntil: new Date(Date.now() + (durationSeconds + 300) * 1e3).toISOString(),
@@ -382,7 +383,7 @@ async function provisionLocalStarterPortfolioCapacity(input) {
382
383
  metadata: { liveAcceptance: true, runId: input.runId, concurrentStarters: true }
383
384
  }, `${key}:allocation-create`);
384
385
  const active = (await input.adminClient.capacityAllocationSets(input.runtime.teamId, { limit: 200 })).payload.items.find((entry) => entry && typeof entry === "object" && entry.status === "active");
385
- await input.adminClient.supersedeCapacityAllocationSet(input.runtime.teamId, String(allocation.payload.id), { expectedActiveAllocationSetId: active?.id ?? null }, `${key}:allocation-supersede`);
386
+ await supersedeInternalCapacityAllocationSet(input.adminClient, input.runtime.teamId, String(allocation.payload.id), { expectedActiveAllocationSetId: active?.id ?? null }, `${key}:allocation-supersede`);
386
387
  availability = await protocol.refreshAvailabilitySession(sessionId, {
387
388
  expectedSequence: availability.payload.sequence,
388
389
  environment: "local",
@@ -1,5 +1,6 @@
1
1
  import { ProviderProtocolClient } from "../../capacity/providers/capacity-provider.js";
2
2
  import { MarketClient } from "../../entrypoints/clients/market-client.js";
3
+ import { createInternalCapacityAllocationSet, supersedeInternalCapacityAllocationSet } from "../../market-client/internal-capacity-allocation.js";
3
4
  import {
4
5
  provisionLocalCapacityAcceptanceProvider,
5
6
  syncLocalAcceptanceAgentClasses
@@ -288,7 +289,7 @@ function createProductionAgentLabExecutor(options) {
288
289
  }, `agent-lab:${input.runId}:grant-create`);
289
290
  await client.transitionCapacityGrant(scope.teamId, grantId, "activate", `agent-lab:${input.runId}:grant-activate`);
290
291
  const allocationId = `agent-lab:${input.runId}:allocation`;
291
- allocation = (await client.createCapacityAllocationSet(scope.teamId, {
292
+ allocation = (await createInternalCapacityAllocationSet(client, scope.teamId, {
292
293
  id: allocationId,
293
294
  effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
294
295
  effectiveUntil: new Date(Date.now() + input.config.workdays.reduce((sum, day) => sum + day.durationSeconds, 0) * 1e3 + 6e5).toISOString(),
@@ -297,7 +298,7 @@ function createProductionAgentLabExecutor(options) {
297
298
  slices: [{ id: `${allocationId}:market`, scope: "project", targetId: scope.projectId, policy: { minPercent: 0, targetPercent: 100, maxPercent: 100, hardCapPercent: 100 } }],
298
299
  metadata: { agentLab: true, runId: input.runId }
299
300
  }, `agent-lab:${input.runId}:allocation-create`)).payload;
300
- await client.supersedeCapacityAllocationSet(scope.teamId, text(allocation.id), { expectedActiveAllocationSetId: null }, `agent-lab:${input.runId}:allocation-activate`);
301
+ await supersedeInternalCapacityAllocationSet(client, scope.teamId, text(allocation.id), { expectedActiveAllocationSetId: null }, `agent-lab:${input.runId}:allocation-activate`);
301
302
  availability = await protocol.refreshAvailabilitySession(sessionId, {
302
303
  expectedSequence: availability.payload.sequence,
303
304
  environment: "local",
@@ -12,6 +12,7 @@ import {
12
12
  } from "../../capacity/providers/capacity-provider.js";
13
13
  import { MarketClient } from "../../entrypoints/clients/market-client.js";
14
14
  import { MarketClientError } from "../../entrypoints/clients/market-client.js";
15
+ import { createInternalCapacityAllocationSet, supersedeInternalCapacityAllocationSet } from "../../market-client/internal-capacity-allocation.js";
15
16
  function object(value) {
16
17
  return value && typeof value === "object" && !Array.isArray(value) ? value : {};
17
18
  }
@@ -346,7 +347,7 @@ async function reconcilePolicy(input) {
346
347
  }
347
348
  if (!allocation) {
348
349
  const target = 100 / allocationProjectIds.length;
349
- const created = await input.client.createCapacityAllocationSet(input.teamId, {
350
+ const created = await createInternalCapacityAllocationSet(input.client, input.teamId, {
350
351
  id: allocationId,
351
352
  effectiveFrom: new Date(Date.now() - 1e3).toISOString(),
352
353
  effectiveUntil: "2100-01-01T00:00:00.000Z",
@@ -359,7 +360,8 @@ async function reconcilePolicy(input) {
359
360
  }
360
361
  if (string(allocation.status) !== "active") {
361
362
  const expectedActiveAllocationSetId = string(activeAllocation?.id);
362
- await input.client.supersedeCapacityAllocationSet(
363
+ await supersedeInternalCapacityAllocationSet(
364
+ input.client,
363
365
  input.teamId,
364
366
  allocationId,
365
367
  { expectedActiveAllocationSetId },
@@ -0,0 +1,2 @@
1
+ import type { McpCompatibilityComparison, McpContractModel } from './contracts.js';
2
+ export declare function compareMcp(baseline: McpContractModel, candidate: McpContractModel): McpCompatibilityComparison;
@@ -0,0 +1,38 @@
1
+ const rank = { unchanged: 0, compatible_addition: 1, breaking: 2 };
2
+ function same(left, right) {
3
+ return JSON.stringify(left) === JSON.stringify(right);
4
+ }
5
+ function compareMcp(baseline, candidate) {
6
+ const findings = [];
7
+ const add = (code, path, message, classification) => findings.push({ code, path, message, classification });
8
+ if (baseline.protocolVersion !== candidate.protocolVersion) add("mcp_protocol_changed", "protocolVersion", "The MCP protocol version changed.", "breaking");
9
+ for (const [name, tool] of Object.entries(baseline.tools)) {
10
+ const next = candidate.tools[name];
11
+ if (!next) {
12
+ add("mcp_tool_removed", `tools.${name}`, "An MCP tool was removed.", "breaking");
13
+ continue;
14
+ }
15
+ if (!same(tool.inputSchema, next.inputSchema)) add("mcp_tool_input_changed", `tools.${name}.inputSchema`, "Tool input changed.", "breaking");
16
+ if (!same(tool.outputSchema, next.outputSchema)) add("mcp_tool_output_changed", `tools.${name}.outputSchema`, "Tool output changed.", "breaking");
17
+ if (next.requiredScopes.some((scope) => !tool.requiredScopes.includes(scope))) add("mcp_tool_scope_escalated", `tools.${name}.requiredScopes`, "Tool scope requirements increased.", "breaking");
18
+ if (tool.riskClass !== next.riskClass) add("mcp_tool_risk_changed", `tools.${name}.riskClass`, "Tool risk classification changed.", "breaking");
19
+ }
20
+ for (const name of Object.keys(candidate.tools).filter((name2) => !(name2 in baseline.tools))) add("mcp_tool_added", `tools.${name}`, "An MCP tool was added.", "compatible_addition");
21
+ for (const [uri, resource] of Object.entries(baseline.resources)) {
22
+ const next = candidate.resources[uri];
23
+ if (!next) add("mcp_resource_removed", `resources.${uri}`, "An MCP resource was removed.", "breaking");
24
+ else if (!same(resource, next)) add("mcp_resource_changed", `resources.${uri}`, "An MCP resource contract changed.", "breaking");
25
+ }
26
+ for (const uri of Object.keys(candidate.resources).filter((uri2) => !(uri2 in baseline.resources))) add("mcp_resource_added", `resources.${uri}`, "An MCP resource was added.", "compatible_addition");
27
+ for (const [name, prompt] of Object.entries(baseline.prompts)) {
28
+ const next = candidate.prompts[name];
29
+ if (!next) add("mcp_prompt_removed", `prompts.${name}`, "An MCP prompt was removed.", "breaking");
30
+ else if (!same(prompt, next)) add("mcp_prompt_changed", `prompts.${name}`, "An MCP prompt contract changed.", "breaking");
31
+ }
32
+ for (const name of Object.keys(candidate.prompts).filter((name2) => !(name2 in baseline.prompts))) add("mcp_prompt_added", `prompts.${name}`, "An MCP prompt was added.", "compatible_addition");
33
+ findings.sort((left, right) => `${left.path}:${left.code}`.localeCompare(`${right.path}:${right.code}`));
34
+ return { classification: findings.reduce((value, finding) => rank[finding.classification] > rank[value] ? finding.classification : value, "unchanged"), findings };
35
+ }
36
+ export {
37
+ compareMcp
38
+ };
@@ -0,0 +1,34 @@
1
+ import type { CompatibilityClassification } from '../contracts.js';
2
+ import type { OAuthScope } from '../../operator-contracts/control-plane-operation.js';
3
+ export interface McpNormalizedTool {
4
+ inputSchema: unknown;
5
+ outputSchema: unknown;
6
+ requiredScopes: OAuthScope[];
7
+ riskClass: string;
8
+ }
9
+ export interface McpNormalizedResource {
10
+ uriTemplate: string;
11
+ operationId: string;
12
+ subscribable: boolean;
13
+ }
14
+ export interface McpNormalizedPrompt {
15
+ argumentSchema: unknown;
16
+ requiredScopes: OAuthScope[];
17
+ }
18
+ export interface McpContractModel {
19
+ schemaVersion: 1;
20
+ protocolVersion: string;
21
+ tools: Record<string, McpNormalizedTool>;
22
+ resources: Record<string, McpNormalizedResource>;
23
+ prompts: Record<string, McpNormalizedPrompt>;
24
+ }
25
+ export interface McpCompatibilityFinding {
26
+ code: string;
27
+ path: string;
28
+ message: string;
29
+ classification: CompatibilityClassification;
30
+ }
31
+ export interface McpCompatibilityComparison {
32
+ classification: CompatibilityClassification;
33
+ findings: McpCompatibilityFinding[];
34
+ }
File without changes
@@ -0,0 +1,3 @@
1
+ export * from './contracts.js';
2
+ export * from './normalize.js';
3
+ export * from './compare.js';
@@ -0,0 +1,3 @@
1
+ export * from "./contracts.js";
2
+ export * from "./normalize.js";
3
+ export * from "./compare.js";
@@ -0,0 +1,4 @@
1
+ import type { McpCatalog } from '../../operator-contracts/mcp.js';
2
+ import type { ControlPlaneOperationDescriptor } from '../../operator-contracts/control-plane-operation.js';
3
+ import type { McpContractModel } from './contracts.js';
4
+ export declare function normalizeMcpCatalog(catalog: McpCatalog, operations: readonly ControlPlaneOperationDescriptor[], schemas: Readonly<Record<string, unknown>>): McpContractModel;
@@ -0,0 +1,30 @@
1
+ import { canonicalizeStandardsValue } from "../canonicalize.js";
2
+ function normalizeMcpCatalog(catalog, operations, schemas) {
3
+ const operationById = new Map(operations.map((operation) => [operation.operationId, operation]));
4
+ return {
5
+ schemaVersion: 1,
6
+ protocolVersion: catalog.protocolVersion,
7
+ tools: Object.fromEntries([...catalog.tools].sort((left, right) => left.name.localeCompare(right.name)).map((tool) => {
8
+ const operation = operationById.get(tool.operationId);
9
+ if (!operation) throw new Error(`MCP tool ${tool.name} references unknown operation ${tool.operationId}.`);
10
+ return [tool.name, {
11
+ inputSchema: canonicalizeStandardsValue(schemas[tool.inputSchemaId] ?? {}),
12
+ outputSchema: canonicalizeStandardsValue(schemas[tool.outputSchemaId] ?? {}),
13
+ requiredScopes: [...operation.oauthScopes].sort(),
14
+ riskClass: operation.riskClass
15
+ }];
16
+ })),
17
+ resources: Object.fromEntries([...catalog.resources].sort((left, right) => left.uriTemplate.localeCompare(right.uriTemplate)).map((resource) => [resource.uriTemplate, {
18
+ uriTemplate: resource.uriTemplate,
19
+ operationId: resource.operationId,
20
+ subscribable: resource.subscribable
21
+ }])),
22
+ prompts: Object.fromEntries([...catalog.prompts].sort((left, right) => left.name.localeCompare(right.name)).map((prompt) => [prompt.name, {
23
+ argumentSchema: canonicalizeStandardsValue(schemas[prompt.argumentSchemaId] ?? {}),
24
+ requiredScopes: [...prompt.requiredScopes].sort()
25
+ }]))
26
+ };
27
+ }
28
+ export {
29
+ normalizeMcpCatalog
30
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.13.0-rc.3",
3
+ "version": "0.13.0-rc.6",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -135,6 +135,10 @@
135
135
  "types": "./dist/standards/openapi/index.d.ts",
136
136
  "default": "./dist/standards/openapi/index.js"
137
137
  },
138
+ "./standards/mcp": {
139
+ "types": "./dist/standards/mcp/index.d.ts",
140
+ "default": "./dist/standards/mcp/index.js"
141
+ },
138
142
  "./operator-contracts": {
139
143
  "types": "./dist/operator-contracts/index.d.ts",
140
144
  "default": "./dist/operator-contracts/index.js"
@@ -215,6 +219,10 @@
215
219
  "types": "./dist/entrypoints/clients/market-client.d.ts",
216
220
  "default": "./dist/entrypoints/clients/market-client.js"
217
221
  },
222
+ "./control-plane-client": {
223
+ "types": "./dist/entrypoints/clients/control-plane-client.d.ts",
224
+ "default": "./dist/entrypoints/clients/control-plane-client.js"
225
+ },
218
226
  "./market-gateway": {
219
227
  "types": "./dist/gateway/index.d.ts",
220
228
  "default": "./dist/gateway/index.js"