@decocms/bindings 1.4.1 → 1.4.2
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/package.json +6 -5
- package/src/core/binder.ts +1 -21
- package/src/core/client/index.ts +0 -1
- package/src/core/client/mcp-client.ts +0 -35
- package/src/core/client/mcp.ts +7 -15
- package/src/core/client/proxy.ts +1 -10
- package/src/core/server-plugin.ts +2 -2
- package/src/index.ts +22 -0
- package/src/well-known/brand.ts +151 -0
- package/src/well-known/language-model.ts +0 -1
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/bindings",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check": "tsc --noEmit",
|
|
7
7
|
"test": "bun test"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@modelcontextprotocol/sdk": "1.
|
|
11
|
-
"@tanstack/react-router": "1.
|
|
12
|
-
"react": "^19.2.
|
|
10
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
11
|
+
"@tanstack/react-router": "1.169.2",
|
|
12
|
+
"react": "^19.2.6",
|
|
13
13
|
"zod": "^4.0.0",
|
|
14
14
|
"zod-from-json-schema": "^0.5.2"
|
|
15
15
|
},
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"./plugin-router": "./src/core/plugin-router.tsx",
|
|
33
33
|
"./ai-gateway": "./src/well-known/ai-gateway.ts",
|
|
34
34
|
"./server-plugin": "./src/core/server-plugin.ts",
|
|
35
|
-
"./trigger": "./src/well-known/trigger.ts"
|
|
35
|
+
"./trigger": "./src/well-known/trigger.ts",
|
|
36
|
+
"./brand": "./src/well-known/brand.ts"
|
|
36
37
|
},
|
|
37
38
|
"engines": {
|
|
38
39
|
"node": ">=24.0.0"
|
package/src/core/binder.ts
CHANGED
|
@@ -24,7 +24,6 @@ export interface ToolBinder<
|
|
|
24
24
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
25
|
TInput = any,
|
|
26
26
|
TReturn extends object | null | boolean = object,
|
|
27
|
-
TStreamable extends boolean = boolean,
|
|
28
27
|
> {
|
|
29
28
|
/** The name of the tool (e.g., "DECO_CHAT_CHANNELS_JOIN") */
|
|
30
29
|
name: TName;
|
|
@@ -33,12 +32,7 @@ export interface ToolBinder<
|
|
|
33
32
|
inputSchema: ZodType<TInput>;
|
|
34
33
|
|
|
35
34
|
/** Optional Zod schema for validating tool output */
|
|
36
|
-
outputSchema?:
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Whether this tool is streamable.
|
|
40
|
-
*/
|
|
41
|
-
streamable?: TStreamable;
|
|
35
|
+
outputSchema?: ZodType<TReturn>;
|
|
42
36
|
|
|
43
37
|
/**
|
|
44
38
|
* Whether this tool is optional in the binding.
|
|
@@ -103,13 +97,6 @@ export const bindingClient = <TDefinition extends readonly ToolBinder[]>(
|
|
|
103
97
|
forClient: (client: ServerClient): MCPClientFetchStub<TDefinition> => {
|
|
104
98
|
return createMCPFetchStub<TDefinition>({
|
|
105
99
|
client,
|
|
106
|
-
streamable: binder.reduce(
|
|
107
|
-
(acc, tool) => {
|
|
108
|
-
acc[tool.name] = tool.streamable === true;
|
|
109
|
-
return acc;
|
|
110
|
-
},
|
|
111
|
-
{} as Record<string, boolean>,
|
|
112
|
-
),
|
|
113
100
|
});
|
|
114
101
|
},
|
|
115
102
|
forConnection: (
|
|
@@ -117,13 +104,6 @@ export const bindingClient = <TDefinition extends readonly ToolBinder[]>(
|
|
|
117
104
|
): MCPClientFetchStub<TDefinition> => {
|
|
118
105
|
return createMCPFetchStub<TDefinition>({
|
|
119
106
|
connection: mcpConnection,
|
|
120
|
-
streamable: binder.reduce(
|
|
121
|
-
(acc, tool) => {
|
|
122
|
-
acc[tool.name] = tool.streamable === true;
|
|
123
|
-
return acc;
|
|
124
|
-
},
|
|
125
|
-
{} as Record<string, boolean>,
|
|
126
|
-
),
|
|
127
107
|
});
|
|
128
108
|
},
|
|
129
109
|
};
|
package/src/core/client/index.ts
CHANGED
|
@@ -47,11 +47,6 @@ export interface ServerClient {
|
|
|
47
47
|
callTool: Client["callTool"];
|
|
48
48
|
listTools: () => Promise<ListToolsResult>;
|
|
49
49
|
};
|
|
50
|
-
callStreamableTool?: (
|
|
51
|
-
tool: string,
|
|
52
|
-
args: Record<string, unknown>,
|
|
53
|
-
signal?: AbortSignal,
|
|
54
|
-
) => Promise<Response>;
|
|
55
50
|
}
|
|
56
51
|
export const createServerClient = async (
|
|
57
52
|
mcpServer: { connection: MCPConnection; name?: string },
|
|
@@ -73,36 +68,6 @@ export const createServerClient = async (
|
|
|
73
68
|
|
|
74
69
|
return {
|
|
75
70
|
client,
|
|
76
|
-
callStreamableTool: (tool, args, signal) => {
|
|
77
|
-
if (mcpServer.connection.type !== "HTTP") {
|
|
78
|
-
throw new Error("HTTP connection required");
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
const headers = new Headers(extraHeaders);
|
|
82
|
-
|
|
83
|
-
if (!headers.has("Authorization")) {
|
|
84
|
-
headers.set("Authorization", `Bearer ${mcpServer.connection.token}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
for (const [key, value] of Object.entries(
|
|
88
|
-
mcpServer.connection.headers ?? {},
|
|
89
|
-
)) {
|
|
90
|
-
headers.set(key, value);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const url = new URL(mcpServer.connection.url);
|
|
94
|
-
// Trim trailing slashes from pathname, ensuring it starts with '/'
|
|
95
|
-
const trimmedPath = url.pathname.replace(/\/+$/, "") || "/";
|
|
96
|
-
url.pathname = `${trimmedPath}/call-tool/${encodeURIComponent(tool)}`;
|
|
97
|
-
|
|
98
|
-
return fetch(url.href, {
|
|
99
|
-
method: "POST",
|
|
100
|
-
redirect: "manual",
|
|
101
|
-
body: JSON.stringify(args),
|
|
102
|
-
headers,
|
|
103
|
-
signal,
|
|
104
|
-
});
|
|
105
|
-
},
|
|
106
71
|
};
|
|
107
72
|
};
|
|
108
73
|
|
package/src/core/client/mcp.ts
CHANGED
|
@@ -3,12 +3,6 @@ import { z } from "zod";
|
|
|
3
3
|
import type { MCPConnection } from "../connection";
|
|
4
4
|
import { createMCPClientProxy } from "./proxy";
|
|
5
5
|
export type { ServerClient } from "./mcp-client";
|
|
6
|
-
export const isStreamableToolBinder = (
|
|
7
|
-
toolBinder: ToolBinder,
|
|
8
|
-
): toolBinder is ToolBinder<string, any, any, true> => {
|
|
9
|
-
return toolBinder.streamable === true;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
6
|
// Default fetcher instance with API_SERVER_URL and API_HEADERS
|
|
13
7
|
export const MCPClient = new Proxy(
|
|
14
8
|
{} as {
|
|
@@ -69,13 +63,13 @@ export type MCPClientFetchStub<TDefinition extends readonly ToolBinder[]> = {
|
|
|
69
63
|
}[]
|
|
70
64
|
>;
|
|
71
65
|
} & {
|
|
72
|
-
[K in TDefinition[number] as K["name"]]: K
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
[K in TDefinition[number] as K["name"]]: K extends ToolBinder<
|
|
67
|
+
string,
|
|
68
|
+
infer TInput,
|
|
69
|
+
infer TReturn
|
|
70
|
+
>
|
|
71
|
+
? (params: TInput, init?: RequestInit) => Promise<Awaited<TReturn>>
|
|
72
|
+
: never;
|
|
79
73
|
};
|
|
80
74
|
|
|
81
75
|
export interface MCPClientRaw {
|
|
@@ -93,7 +87,6 @@ export type JSONSchemaToZodConverter = (jsonSchema: any) => z.ZodTypeAny;
|
|
|
93
87
|
|
|
94
88
|
export interface CreateStubForClientAPIOptions {
|
|
95
89
|
client: ServerClient;
|
|
96
|
-
streamable?: Record<string, boolean>;
|
|
97
90
|
debugId?: () => string;
|
|
98
91
|
getErrorByStatusCode?: (
|
|
99
92
|
statusCode: number,
|
|
@@ -105,7 +98,6 @@ export interface CreateStubForClientAPIOptions {
|
|
|
105
98
|
|
|
106
99
|
export interface CreateStubForConnectionAPIOptions {
|
|
107
100
|
connection: MCPConnection;
|
|
108
|
-
streamable?: Record<string, boolean>;
|
|
109
101
|
debugId?: () => string;
|
|
110
102
|
createServerClient?: (
|
|
111
103
|
mcpServer: { connection: MCPConnection; name?: string },
|
package/src/core/client/proxy.ts
CHANGED
|
@@ -74,16 +74,7 @@ export function createMCPClientProxy<T extends Record<string, unknown>>(
|
|
|
74
74
|
? { "x-trace-debug-id": debugId }
|
|
75
75
|
: undefined;
|
|
76
76
|
|
|
77
|
-
const { client
|
|
78
|
-
|
|
79
|
-
if (options?.streamable?.[String(toolName)]) {
|
|
80
|
-
if (!callStreamableTool) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`Tool ${String(toolName)} requires streaming support but client doesn't provide callStreamableTool`,
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
return await callStreamableTool(String(toolName), args);
|
|
86
|
-
}
|
|
77
|
+
const { client } = await createClient(extraHeaders);
|
|
87
78
|
|
|
88
79
|
const { structuredContent, isError, content } = await client.callTool({
|
|
89
80
|
name: String(toolName),
|
|
@@ -16,9 +16,9 @@ import type { Hono } from "hono";
|
|
|
16
16
|
import type { Kysely } from "kysely";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Subset of
|
|
19
|
+
* Subset of StudioContext exposed to server plugin tool handlers.
|
|
20
20
|
*
|
|
21
|
-
* Plugins receive the full
|
|
21
|
+
* Plugins receive the full StudioContext at runtime but should only depend on
|
|
22
22
|
* these properties. This keeps the plugin contract stable and avoids coupling
|
|
23
23
|
* plugins to Mesh internals (db, vault, tracer, etc.).
|
|
24
24
|
*/
|
package/src/index.ts
CHANGED
|
@@ -123,3 +123,25 @@ export {
|
|
|
123
123
|
|
|
124
124
|
// Re-export workflow binding types
|
|
125
125
|
export { WORKFLOWS_COLLECTION_BINDING } from "./well-known/workflow";
|
|
126
|
+
|
|
127
|
+
// Re-export brand binding types (for reading org brand context)
|
|
128
|
+
export {
|
|
129
|
+
BrandColorsSchema,
|
|
130
|
+
type BrandColors,
|
|
131
|
+
BrandFontsSchema,
|
|
132
|
+
type BrandFonts,
|
|
133
|
+
BrandAssetsSchema,
|
|
134
|
+
type BrandAssets,
|
|
135
|
+
BrandSchema,
|
|
136
|
+
type Brand,
|
|
137
|
+
BrandGetInputSchema,
|
|
138
|
+
type BrandGetInput,
|
|
139
|
+
type BrandGetOutput,
|
|
140
|
+
BrandListInputSchema,
|
|
141
|
+
type BrandListInput,
|
|
142
|
+
BrandListOutputSchema,
|
|
143
|
+
type BrandListOutput,
|
|
144
|
+
BRAND_BINDING,
|
|
145
|
+
BrandBinding,
|
|
146
|
+
type BrandBindingClient,
|
|
147
|
+
} from "./well-known/brand";
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand Well-Known Binding
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface for reading brand context (colors, fonts, assets,
|
|
5
|
+
* voice) from an organization. External MCPs that need brand awareness
|
|
6
|
+
* can declare a dependency on this binding and call BRAND_GET / BRAND_LIST
|
|
7
|
+
* to pull structured brand data on demand.
|
|
8
|
+
*
|
|
9
|
+
* This binding includes:
|
|
10
|
+
* - BRAND_GET: Get a brand by ID (or the org default)
|
|
11
|
+
* - BRAND_LIST: List all active brands (optional)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import { bindingClient, type ToolBinder } from "../core/binder";
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Brand Sub-Schemas
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
export const BrandColorsSchema = z.object({
|
|
22
|
+
primary: z.string().optional().describe("Primary brand color (hex)"),
|
|
23
|
+
secondary: z.string().optional().describe("Secondary brand color (hex)"),
|
|
24
|
+
accent: z.string().optional().describe("Accent / highlight color (hex)"),
|
|
25
|
+
background: z.string().optional().describe("Background color (hex)"),
|
|
26
|
+
foreground: z.string().optional().describe("Foreground / text color (hex)"),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type BrandColors = z.infer<typeof BrandColorsSchema>;
|
|
30
|
+
|
|
31
|
+
export const BrandFontsSchema = z.object({
|
|
32
|
+
heading: z.string().optional().describe("Font family for headings"),
|
|
33
|
+
body: z.string().optional().describe("Font family for body text"),
|
|
34
|
+
code: z.string().optional().describe("Font family for code / monospace"),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type BrandFonts = z.infer<typeof BrandFontsSchema>;
|
|
38
|
+
|
|
39
|
+
export const BrandAssetsSchema = z.object({
|
|
40
|
+
logo: z.string().optional().describe("Logo URL"),
|
|
41
|
+
favicon: z.string().optional().describe("Favicon URL"),
|
|
42
|
+
ogImage: z.string().optional().describe("Open Graph image URL"),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export type BrandAssets = z.infer<typeof BrandAssetsSchema>;
|
|
46
|
+
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// Brand Schema
|
|
49
|
+
// ============================================================================
|
|
50
|
+
|
|
51
|
+
export const BrandSchema = z.object({
|
|
52
|
+
id: z.string(),
|
|
53
|
+
name: z.string().describe("Brand / company name"),
|
|
54
|
+
domain: z.string().optional().describe("Company domain (e.g. example.com)"),
|
|
55
|
+
colors: BrandColorsSchema.optional().describe("Semantic color palette"),
|
|
56
|
+
fonts: BrandFontsSchema.optional().describe("Font families by role"),
|
|
57
|
+
assets: BrandAssetsSchema.optional().describe("Visual identity assets"),
|
|
58
|
+
overview: z.string().optional().describe("Company overview / description"),
|
|
59
|
+
tagline: z.string().optional().describe("Brand tagline"),
|
|
60
|
+
tone: z.string().optional().describe("Tone of voice description"),
|
|
61
|
+
metadata: z
|
|
62
|
+
.record(z.string(), z.unknown())
|
|
63
|
+
.optional()
|
|
64
|
+
.describe("Extra design tokens and metadata"),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export type Brand = z.infer<typeof BrandSchema>;
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// BRAND_GET Schemas
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
export const BrandGetInputSchema = z.object({
|
|
74
|
+
id: z
|
|
75
|
+
.string()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe("Brand ID. Omit to get the default brand."),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
export type BrandGetInput = z.infer<typeof BrandGetInputSchema>;
|
|
81
|
+
|
|
82
|
+
export type BrandGetOutput = z.infer<typeof BrandSchema>;
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// BRAND_LIST Schemas
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
export const BrandListInputSchema = z.object({});
|
|
89
|
+
|
|
90
|
+
export type BrandListInput = z.infer<typeof BrandListInputSchema>;
|
|
91
|
+
|
|
92
|
+
export const BrandListOutputSchema = z.object({
|
|
93
|
+
items: z.array(BrandSchema),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
export type BrandListOutput = z.infer<typeof BrandListOutputSchema>;
|
|
97
|
+
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Brand Binding
|
|
100
|
+
// ============================================================================
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Brand Binding
|
|
104
|
+
*
|
|
105
|
+
* Defines the interface for reading brand context from an organization.
|
|
106
|
+
*
|
|
107
|
+
* Required tools:
|
|
108
|
+
* - BRAND_GET: Get a single brand by ID, or the default brand when no ID
|
|
109
|
+
*
|
|
110
|
+
* Optional tools:
|
|
111
|
+
* - BRAND_LIST: List all active brands
|
|
112
|
+
*/
|
|
113
|
+
export const BRAND_BINDING = [
|
|
114
|
+
{
|
|
115
|
+
name: "BRAND_GET" as const,
|
|
116
|
+
inputSchema: BrandGetInputSchema,
|
|
117
|
+
outputSchema: BrandSchema,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "BRAND_LIST" as const,
|
|
121
|
+
inputSchema: BrandListInputSchema,
|
|
122
|
+
outputSchema: BrandListOutputSchema,
|
|
123
|
+
opt: true,
|
|
124
|
+
},
|
|
125
|
+
] satisfies ToolBinder[];
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Brand Binding Client
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* import { BrandBinding } from "@decocms/bindings/brand";
|
|
133
|
+
*
|
|
134
|
+
* const client = BrandBinding.forConnection(connection);
|
|
135
|
+
*
|
|
136
|
+
* // Get the default brand
|
|
137
|
+
* const brand = await client.BRAND_GET({});
|
|
138
|
+
*
|
|
139
|
+
* // Get a specific brand
|
|
140
|
+
* const brand = await client.BRAND_GET({ id: "acme-corp" });
|
|
141
|
+
*
|
|
142
|
+
* // List all brands
|
|
143
|
+
* const { items } = await client.BRAND_LIST({});
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
export const BrandBinding = bindingClient(BRAND_BINDING);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Type helper for the Brand binding client
|
|
150
|
+
*/
|
|
151
|
+
export type BrandBindingClient = ReturnType<typeof BrandBinding.forConnection>;
|