@unstable-dev/unmeshed-mcp 0.1.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.
Files changed (47) hide show
  1. package/README.md +64 -0
  2. package/dist/auth.d.ts +6 -0
  3. package/dist/auth.js +11 -0
  4. package/dist/client.d.ts +46 -0
  5. package/dist/client.js +97 -0
  6. package/dist/config.d.ts +10 -0
  7. package/dist/config.js +31 -0
  8. package/dist/get-docs.d.ts +8 -0
  9. package/dist/get-docs.js +64 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +35 -0
  12. package/dist/server.d.ts +4 -0
  13. package/dist/server.js +203 -0
  14. package/knowledge/README.md +16 -0
  15. package/knowledge/SKILL.md +359 -0
  16. package/knowledge/assets/patterns.md +637 -0
  17. package/knowledge/execution/debugging-guide.md +18 -0
  18. package/knowledge/execution/process-run.schema.md +24 -0
  19. package/knowledge/execution/step-run.schema.md +21 -0
  20. package/knowledge/process-definition.schema.md +36 -0
  21. package/knowledge/references/integrations.md +914 -0
  22. package/knowledge/references/steps-knowledge.md +834 -0
  23. package/knowledge/step-definition.schema.md +140 -0
  24. package/knowledge/step-output-paths.md +45 -0
  25. package/knowledge/steps/DECISION_ENGINE.md +248 -0
  26. package/knowledge/steps/DEPENDSON.md +296 -0
  27. package/knowledge/steps/EXIT.md +220 -0
  28. package/knowledge/steps/FAIL.md +198 -0
  29. package/knowledge/steps/FLOW_GATEWAY.md +405 -0
  30. package/knowledge/steps/FOREACH.md +250 -0
  31. package/knowledge/steps/HTTP.md +183 -0
  32. package/knowledge/steps/JAVASCRIPT.md +192 -0
  33. package/knowledge/steps/JQ.md +189 -0
  34. package/knowledge/steps/LIST.md +279 -0
  35. package/knowledge/steps/NOOP.md +165 -0
  36. package/knowledge/steps/PARALLEL.md +366 -0
  37. package/knowledge/steps/PYTHON.md +206 -0
  38. package/knowledge/steps/SEND_RESPONSE.md +301 -0
  39. package/knowledge/steps/SQLITE.md +301 -0
  40. package/knowledge/steps/SUB_PROCESS.md +296 -0
  41. package/knowledge/steps/SWITCH.md +369 -0
  42. package/knowledge/steps/UPDATE_STEP.md +257 -0
  43. package/knowledge/steps/WAIT.md +218 -0
  44. package/knowledge/steps/WHILE.md +328 -0
  45. package/knowledge/steps/WORKER.md +233 -0
  46. package/knowledge/system-prompt.md +274 -0
  47. package/package.json +39 -0
package/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # Unmeshed MCP server
2
+
3
+ Use Unmeshed from Cursor, Claude Code, Codex, and other MCP clients. You do not need the Unmeshed source repo or the Unmeshed CLI.
4
+
5
+ ## Setup
6
+
7
+ 1. In Unmeshed, create an **API client**. Copy the client id and auth token.
8
+ 2. Add this to your MCP config:
9
+
10
+ ```json
11
+ {
12
+ "mcpServers": {
13
+ "unmeshed": {
14
+ "command": "npx",
15
+ "args": ["-y", "@unmeshed/mcp"],
16
+ "env": {
17
+ "UNMESHED_URL": "https://your-org.unmeshed.io",
18
+ "UNMESHED_CLIENT_ID": "your-client-id",
19
+ "UNMESHED_AUTH_TOKEN": "your-auth-token"
20
+ }
21
+ }
22
+ }
23
+ }
24
+ ```
25
+
26
+ That is the whole setup: your Unmeshed URL, plus the same client id and auth token used by `@unmeshed/sdk`.
27
+
28
+ Until `@unmeshed/mcp` is published, point `command` at Node and `args` at this package’s built file:
29
+
30
+ ```json
31
+ {
32
+ "command": "node",
33
+ "args": ["/ABS/PATH/TO/unmeshed-mcp/dist/index.js"]
34
+ }
35
+ ```
36
+
37
+ Use `http://localhost:8080` if the engine is running locally.
38
+
39
+ ## Tools
40
+
41
+ | Tool | What it does |
42
+ |---|---|
43
+ | `get_docs` | Authoring docs (schemas and step types). Call with no topic to list topics. |
44
+ | `list_processes` | List process definitions. |
45
+ | `get_process` | Fetch one process definition. |
46
+ | `save_process` | Create or update a process definition. |
47
+ | `run_process` | Start a process. |
48
+ | `get_run` | Fetch a process run by id. |
49
+
50
+ Call `get_docs` before writing a process. Then `save_process`, then `run_process`, then `get_run`.
51
+
52
+ ## Try it
53
+
54
+ 1. What processes do I have?
55
+ 2. Show me the process definition schema.
56
+ 3. Create a hello-world process, save it, and run it.
57
+ 4. Did that run succeed?
58
+
59
+ ## Develop
60
+
61
+ ```bash
62
+ npm test
63
+ npm run build
64
+ ```
package/dist/auth.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Matches the Unmeshed JS/Java SDK:
3
+ * Authorization: Bearer client.sdk.{clientId}.{sha256(authToken)}
4
+ */
5
+ export declare function createSecureHash(input: string): string;
6
+ export declare function buildAuthorizationHeader(clientId: string, authToken: string): string;
package/dist/auth.js ADDED
@@ -0,0 +1,11 @@
1
+ import { createHash } from "node:crypto";
2
+ /**
3
+ * Matches the Unmeshed JS/Java SDK:
4
+ * Authorization: Bearer client.sdk.{clientId}.{sha256(authToken)}
5
+ */
6
+ export function createSecureHash(input) {
7
+ return createHash("sha256").update(input, "utf8").digest("hex");
8
+ }
9
+ export function buildAuthorizationHeader(clientId, authToken) {
10
+ return `Bearer client.sdk.${clientId}.${createSecureHash(authToken)}`;
11
+ }
@@ -0,0 +1,46 @@
1
+ import type { UnmeshedConfig } from "./config.js";
2
+ export type FetchLike = typeof fetch;
3
+ export declare class UnmeshedApiError extends Error {
4
+ readonly status: number;
5
+ readonly body: unknown;
6
+ constructor(status: number, message: string, body: unknown);
7
+ }
8
+ export type ProcessDefinitionSummary = {
9
+ namespace: unknown;
10
+ name: unknown;
11
+ version: unknown;
12
+ type: unknown;
13
+ description: unknown;
14
+ };
15
+ export type ProcessDefinition = Record<string, unknown> & {
16
+ namespace?: string;
17
+ name?: string;
18
+ version?: number;
19
+ type?: string;
20
+ description?: string;
21
+ steps?: unknown;
22
+ };
23
+ export type RunProcessRequest = {
24
+ name: string;
25
+ namespace?: string;
26
+ version?: number;
27
+ requestId?: string;
28
+ correlationId?: string;
29
+ input?: Record<string, unknown>;
30
+ };
31
+ export declare class UnmeshedClient {
32
+ private readonly baseUrl;
33
+ private readonly authorization;
34
+ private readonly fetchImpl;
35
+ constructor(config: UnmeshedConfig, fetchImpl?: FetchLike);
36
+ listProcessDefinitions(namespace?: string): Promise<{
37
+ count: number;
38
+ definitions: ProcessDefinitionSummary[];
39
+ }>;
40
+ getProcessDefinition(namespace: string, name: string, version?: number): Promise<ProcessDefinition>;
41
+ createProcessDefinition(definition: ProcessDefinition): Promise<ProcessDefinition>;
42
+ updateProcessDefinition(definition: ProcessDefinition): Promise<ProcessDefinition | null>;
43
+ runProcessAsync(request: RunProcessRequest): Promise<unknown>;
44
+ getProcessContext(processId: number, includeSteps?: boolean): Promise<unknown>;
45
+ private request;
46
+ }
package/dist/client.js ADDED
@@ -0,0 +1,97 @@
1
+ import { buildAuthorizationHeader } from "./auth.js";
2
+ export class UnmeshedApiError extends Error {
3
+ status;
4
+ body;
5
+ constructor(status, message, body) {
6
+ super(message);
7
+ this.name = "UnmeshedApiError";
8
+ this.status = status;
9
+ this.body = body;
10
+ }
11
+ }
12
+ export class UnmeshedClient {
13
+ baseUrl;
14
+ authorization;
15
+ fetchImpl;
16
+ constructor(config, fetchImpl = fetch) {
17
+ this.baseUrl = config.baseUrl.replace(/\/+$/, "");
18
+ this.authorization = buildAuthorizationHeader(config.clientId, config.authToken);
19
+ this.fetchImpl = fetchImpl;
20
+ }
21
+ async listProcessDefinitions(namespace) {
22
+ const query = namespace ? `?namespace=${encodeURIComponent(namespace)}` : "";
23
+ const definitions = await this.request("GET", `/api/processDefinitions${query}`);
24
+ const list = Array.isArray(definitions) ? definitions : [];
25
+ return {
26
+ count: list.length,
27
+ definitions: list.map((pd) => ({
28
+ namespace: pd.namespace,
29
+ name: pd.name,
30
+ version: pd.version,
31
+ type: pd.type,
32
+ description: pd.description ?? null,
33
+ })),
34
+ };
35
+ }
36
+ async getProcessDefinition(namespace, name, version) {
37
+ const query = version != null ? `?version=${encodeURIComponent(String(version))}` : "";
38
+ return this.request("GET", `/api/processDefinitions/${encodeURIComponent(namespace)}/${encodeURIComponent(name)}${query}`);
39
+ }
40
+ async createProcessDefinition(definition) {
41
+ return this.request("POST", "/api/processDefinitions", definition);
42
+ }
43
+ async updateProcessDefinition(definition) {
44
+ return this.request("PUT", "/api/processDefinitions", definition);
45
+ }
46
+ async runProcessAsync(request) {
47
+ return this.request("POST", "/api/process/runAsync", {
48
+ name: request.name,
49
+ namespace: request.namespace ?? "default",
50
+ version: request.version ?? null,
51
+ requestId: request.requestId,
52
+ correlationId: request.correlationId,
53
+ input: request.input ?? {},
54
+ });
55
+ }
56
+ async getProcessContext(processId, includeSteps = false) {
57
+ const query = new URLSearchParams({
58
+ includeSteps: String(includeSteps),
59
+ hideLargeValues: "true",
60
+ });
61
+ return this.request("GET", `/api/process/context/${encodeURIComponent(String(processId))}?${query}`);
62
+ }
63
+ async request(method, path, body) {
64
+ const response = await this.fetchImpl(`${this.baseUrl}${path}`, {
65
+ method,
66
+ headers: {
67
+ Authorization: this.authorization,
68
+ Accept: "application/json",
69
+ ...(body !== undefined ? { "Content-Type": "application/json" } : {}),
70
+ },
71
+ body: body !== undefined ? JSON.stringify(body) : undefined,
72
+ });
73
+ const text = await response.text();
74
+ const parsed = parseJsonOrText(text);
75
+ if (response.status === 304) {
76
+ return null;
77
+ }
78
+ if (!response.ok) {
79
+ const message = typeof parsed === "object" && parsed !== null && "message" in parsed
80
+ ? String(parsed.message)
81
+ : `Unmeshed API ${method} ${path} failed with ${response.status}`;
82
+ throw new UnmeshedApiError(response.status, message, parsed);
83
+ }
84
+ return parsed;
85
+ }
86
+ }
87
+ function parseJsonOrText(text) {
88
+ if (!text) {
89
+ return null;
90
+ }
91
+ try {
92
+ return JSON.parse(text);
93
+ }
94
+ catch {
95
+ return text;
96
+ }
97
+ }
@@ -0,0 +1,10 @@
1
+ export type UnmeshedConfig = {
2
+ baseUrl: string;
3
+ clientId: string;
4
+ authToken: string;
5
+ };
6
+ export declare class ConfigError extends Error {
7
+ constructor(message: string);
8
+ }
9
+ export declare function normalizeBaseUrl(baseUrl: string): string;
10
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): UnmeshedConfig;
package/dist/config.js ADDED
@@ -0,0 +1,31 @@
1
+ export class ConfigError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "ConfigError";
5
+ }
6
+ }
7
+ export function normalizeBaseUrl(baseUrl) {
8
+ return baseUrl.replace(/\/+$/, "");
9
+ }
10
+ export function loadConfig(env = process.env) {
11
+ const baseUrl = env.UNMESHED_URL?.trim() || env.UNMESHED_BASE_URL?.trim();
12
+ const clientId = env.UNMESHED_CLIENT_ID?.trim();
13
+ const authToken = env.UNMESHED_AUTH_TOKEN?.trim();
14
+ const missing = [];
15
+ if (!baseUrl)
16
+ missing.push("UNMESHED_URL");
17
+ if (!clientId)
18
+ missing.push("UNMESHED_CLIENT_ID");
19
+ if (!authToken)
20
+ missing.push("UNMESHED_AUTH_TOKEN");
21
+ if (missing.length > 0) {
22
+ throw new ConfigError(`Missing required environment variables: ${missing.join(", ")}. ` +
23
+ "Create an API client in Unmeshed, then set UNMESHED_URL (your instance, for example https://your-org.unmeshed.io), " +
24
+ "UNMESHED_CLIENT_ID, and UNMESHED_AUTH_TOKEN.");
25
+ }
26
+ return {
27
+ baseUrl: normalizeBaseUrl(baseUrl),
28
+ clientId: clientId,
29
+ authToken: authToken,
30
+ };
31
+ }
@@ -0,0 +1,8 @@
1
+ export declare function defaultKnowledgeDir(): string;
2
+ export declare function listDocTopics(knowledgeDir: string): string[];
3
+ export declare function getDoc(knowledgeDir: string, topic?: string): {
4
+ topics: string[];
5
+ } | {
6
+ topic: string;
7
+ content: string;
8
+ };
@@ -0,0 +1,64 @@
1
+ import { readdirSync, readFileSync, statSync } from "node:fs";
2
+ import { dirname, join, relative, sep } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const MARKDOWN_EXT = ".md";
5
+ export function defaultKnowledgeDir() {
6
+ const here = dirname(fileURLToPath(import.meta.url));
7
+ return join(here, "..", "knowledge");
8
+ }
9
+ export function listDocTopics(knowledgeDir) {
10
+ return collectMarkdownFiles(knowledgeDir)
11
+ .map((absPath) => toTopic(knowledgeDir, absPath))
12
+ .sort((a, b) => a.localeCompare(b));
13
+ }
14
+ export function getDoc(knowledgeDir, topic) {
15
+ const topics = listDocTopics(knowledgeDir);
16
+ const trimmed = topic?.trim();
17
+ if (!trimmed) {
18
+ return { topics };
19
+ }
20
+ const resolved = resolveTopic(topics, trimmed);
21
+ if (!resolved) {
22
+ return {
23
+ topics,
24
+ };
25
+ }
26
+ const absPath = join(knowledgeDir, `${resolved.replaceAll("/", sep)}${MARKDOWN_EXT}`);
27
+ return {
28
+ topic: resolved,
29
+ content: readFileSync(absPath, "utf8"),
30
+ };
31
+ }
32
+ function collectMarkdownFiles(dir) {
33
+ const entries = readdirSync(dir);
34
+ const files = [];
35
+ for (const entry of entries) {
36
+ const abs = join(dir, entry);
37
+ const stats = statSync(abs);
38
+ if (stats.isDirectory()) {
39
+ files.push(...collectMarkdownFiles(abs));
40
+ }
41
+ else if (entry.endsWith(MARKDOWN_EXT) && !entry.startsWith("_")) {
42
+ files.push(abs);
43
+ }
44
+ }
45
+ return files;
46
+ }
47
+ function toTopic(knowledgeDir, absPath) {
48
+ const rel = relative(knowledgeDir, absPath);
49
+ return rel.slice(0, -MARKDOWN_EXT.length).split(sep).join("/");
50
+ }
51
+ function resolveTopic(topics, requested) {
52
+ const normalized = requested.replace(/\.md$/i, "").replaceAll("\\", "/");
53
+ const exact = topics.find((topic) => topic === normalized);
54
+ if (exact) {
55
+ return exact;
56
+ }
57
+ const caseInsensitive = topics.find((topic) => topic.toLowerCase() === normalized.toLowerCase());
58
+ if (caseInsensitive) {
59
+ return caseInsensitive;
60
+ }
61
+ const suffix = topics.find((topic) => topic.toLowerCase() === normalized.toLowerCase() ||
62
+ topic.toLowerCase().endsWith(`/${normalized.toLowerCase()}`));
63
+ return suffix;
64
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export declare function main(): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import { pathToFileURL } from "node:url";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { UnmeshedClient } from "./client.js";
5
+ import { ConfigError, loadConfig } from "./config.js";
6
+ import { createUnmeshedMcpServer } from "./server.js";
7
+ export async function main() {
8
+ let config;
9
+ try {
10
+ config = loadConfig();
11
+ }
12
+ catch (error) {
13
+ const message = error instanceof ConfigError ? error.message : String(error);
14
+ process.stderr.write(`${message}\n`);
15
+ process.exit(1);
16
+ }
17
+ const client = new UnmeshedClient(config);
18
+ const server = createUnmeshedMcpServer(client);
19
+ const transport = new StdioServerTransport();
20
+ await server.connect(transport);
21
+ }
22
+ function isDirectRun() {
23
+ const entry = process.argv[1];
24
+ if (!entry) {
25
+ return false;
26
+ }
27
+ return import.meta.url === pathToFileURL(entry).href;
28
+ }
29
+ if (isDirectRun()) {
30
+ main().catch((error) => {
31
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
32
+ process.stderr.write(`${message}\n`);
33
+ process.exit(1);
34
+ });
35
+ }
@@ -0,0 +1,4 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type UnmeshedClient } from "./client.js";
3
+ export declare const SERVER_INSTRUCTIONS: string;
4
+ export declare function createUnmeshedMcpServer(client: UnmeshedClient, knowledgeDir?: string): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,203 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { UnmeshedApiError } from "./client.js";
4
+ import { defaultKnowledgeDir, getDoc } from "./get-docs.js";
5
+ export const SERVER_INSTRUCTIONS = [
6
+ "Call get_docs before writing a process definition.",
7
+ "Use get_docs topics process-definition.schema, step-definition.schema, and steps/<TYPE> so you do not invent Unmeshed step types.",
8
+ "Then save_process, then run_process, then get_run to inspect the result.",
9
+ "Use list_processes and get_process to inspect what already exists.",
10
+ ].join(" ");
11
+ export function createUnmeshedMcpServer(client, knowledgeDir = defaultKnowledgeDir()) {
12
+ const server = new McpServer({
13
+ name: "unmeshed",
14
+ version: "0.1.0",
15
+ }, {
16
+ instructions: SERVER_INSTRUCTIONS,
17
+ });
18
+ server.registerTool("list_processes", {
19
+ description: "List Unmeshed process definitions. Returns name, namespace, version, type, and description only.",
20
+ inputSchema: {
21
+ namespace: z
22
+ .string()
23
+ .optional()
24
+ .describe("Optional namespace filter. Omit to list across namespaces."),
25
+ },
26
+ }, async ({ namespace }) => jsonResult(() => client.listProcessDefinitions(namespace)));
27
+ server.registerTool("get_process", {
28
+ description: "Fetch a single Unmeshed process definition by namespace and name. Omit version to get the latest.",
29
+ inputSchema: {
30
+ namespace: z.string().optional().describe("Namespace. Defaults to default."),
31
+ name: z.string().describe("Process definition name."),
32
+ version: z.number().int().optional().describe("Optional version. Latest if omitted."),
33
+ },
34
+ }, async ({ namespace, name, version }) => jsonResult(() => client.getProcessDefinition(namespace?.trim() || "default", name, version)));
35
+ server.registerTool("save_process", {
36
+ description: "Create or update an Unmeshed process definition. Call get_docs first. Use mode=create for a new process and mode=update when changing an existing version.",
37
+ inputSchema: {
38
+ mode: z
39
+ .enum(["create", "update"])
40
+ .optional()
41
+ .describe("create (POST) or update (PUT). Defaults to create."),
42
+ definition: z
43
+ .record(z.string(), z.unknown())
44
+ .describe("Full process definition JSON. Required fields: name, namespace, type, steps."),
45
+ },
46
+ }, async ({ mode, definition }) => jsonResult(async () => {
47
+ const prepared = prepareDefinition(definition, mode ?? "create");
48
+ if (mode === "update") {
49
+ const updated = await client.updateProcessDefinition(prepared);
50
+ if (updated == null) {
51
+ return {
52
+ ok: true,
53
+ unchanged: true,
54
+ message: "Process definition was not modified.",
55
+ namespace: prepared.namespace,
56
+ name: prepared.name,
57
+ version: prepared.version,
58
+ };
59
+ }
60
+ return {
61
+ ok: true,
62
+ message: "Process definition updated successfully.",
63
+ namespace: updated.namespace,
64
+ name: updated.name,
65
+ version: updated.version,
66
+ };
67
+ }
68
+ const created = await client.createProcessDefinition(prepared);
69
+ return {
70
+ ok: true,
71
+ message: "Process definition created successfully.",
72
+ namespace: created.namespace,
73
+ name: created.name,
74
+ version: created.version,
75
+ };
76
+ }));
77
+ server.registerTool("run_process", {
78
+ description: "Start a saved Unmeshed process asynchronously. Returns the new process id. Use get_run to inspect status later.",
79
+ inputSchema: {
80
+ name: z.string().describe("Process definition name."),
81
+ namespace: z.string().optional().describe("Namespace. Defaults to default."),
82
+ version: z.number().int().optional().describe("Optional version. Latest if omitted."),
83
+ input: z
84
+ .record(z.string(), z.unknown())
85
+ .optional()
86
+ .describe("Process input object. Pass the user-supplied payload here."),
87
+ correlationId: z.string().optional(),
88
+ requestId: z.string().optional(),
89
+ },
90
+ }, async (args) => jsonResult(() => client.runProcessAsync({
91
+ name: args.name,
92
+ namespace: args.namespace?.trim() || "default",
93
+ version: args.version,
94
+ input: args.input,
95
+ correlationId: args.correlationId,
96
+ requestId: args.requestId,
97
+ })));
98
+ server.registerTool("get_run", {
99
+ description: "Fetch a process run by process id. Set includeSteps=true for step outputs and debugging.",
100
+ inputSchema: {
101
+ processId: z.number().int().describe("Process execution id returned by run_process."),
102
+ includeSteps: z
103
+ .boolean()
104
+ .optional()
105
+ .describe("Include step records and outputs. Defaults to false."),
106
+ },
107
+ }, async ({ processId, includeSteps }) => jsonResult(() => client.getProcessContext(processId, includeSteps ?? false)));
108
+ server.registerTool("get_docs", {
109
+ description: "Unmeshed authoring docs (schemas, step types, patterns). Call with no topic to list topics. Use topics like process-definition.schema, step-definition.schema, or steps/HTTP.",
110
+ inputSchema: {
111
+ topic: z
112
+ .string()
113
+ .optional()
114
+ .describe("Optional doc topic. Omit to list all topics."),
115
+ },
116
+ }, async ({ topic }) => {
117
+ const result = getDoc(knowledgeDir, topic);
118
+ if ("content" in result) {
119
+ return {
120
+ content: [{ type: "text", text: result.content }],
121
+ };
122
+ }
123
+ if (topic?.trim()) {
124
+ return {
125
+ isError: true,
126
+ content: [
127
+ {
128
+ type: "text",
129
+ text: JSON.stringify({
130
+ ok: false,
131
+ error: "unknown_topic",
132
+ message: `Unknown topic "${topic}". Use one of the listed topics.`,
133
+ topics: result.topics,
134
+ }, null, 2),
135
+ },
136
+ ],
137
+ };
138
+ }
139
+ return {
140
+ content: [
141
+ {
142
+ type: "text",
143
+ text: JSON.stringify({
144
+ ok: true,
145
+ topics: result.topics,
146
+ hint: "Call get_docs again with a topic, for example process-definition.schema or steps/HTTP.",
147
+ }, null, 2),
148
+ },
149
+ ],
150
+ };
151
+ });
152
+ return server;
153
+ }
154
+ function prepareDefinition(definition, mode) {
155
+ const prepared = { ...definition };
156
+ if (!prepared.namespace || String(prepared.namespace).trim() === "") {
157
+ prepared.namespace = "default";
158
+ }
159
+ if (!prepared.type) {
160
+ prepared.type = "API_ORCHESTRATION";
161
+ }
162
+ if (prepared.version == null && mode === "create") {
163
+ prepared.version = 1;
164
+ }
165
+ return prepared;
166
+ }
167
+ async function jsonResult(run) {
168
+ try {
169
+ const value = await run();
170
+ return {
171
+ content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
172
+ };
173
+ }
174
+ catch (error) {
175
+ return {
176
+ isError: true,
177
+ content: [{ type: "text", text: JSON.stringify(toErrorPayload(error), null, 2) }],
178
+ };
179
+ }
180
+ }
181
+ function toErrorPayload(error) {
182
+ if (error instanceof UnmeshedApiError) {
183
+ return {
184
+ ok: false,
185
+ error: "unmeshed_api_error",
186
+ status: error.status,
187
+ message: error.message,
188
+ body: error.body,
189
+ };
190
+ }
191
+ if (error instanceof Error) {
192
+ return {
193
+ ok: false,
194
+ error: error.name,
195
+ message: error.message,
196
+ };
197
+ }
198
+ return {
199
+ ok: false,
200
+ error: "unknown_error",
201
+ message: String(error),
202
+ };
203
+ }
@@ -0,0 +1,16 @@
1
+ # Schema-Driven Assistant Knowledge
2
+
3
+ This directory is the source of truth for Unmeshed Assistant's workflow
4
+ generation and process-run debugging knowledge.
5
+
6
+ Use it as a registry:
7
+ - `process-definition.schema.md` defines the top-level process definition contract.
8
+ - `step-definition.schema.md` defines the shared step contract.
9
+ - `step-output-paths.md` defines how step outputs are referenced.
10
+ - `steps/<STEP_TYPE>.md` defines one step type at a time.
11
+ - `execution/*.md` defines executed process/run/step records for debugging.
12
+
13
+ When adding a new step type, add a file under `steps/` using
14
+ `steps/_TEMPLATE.md`, then add backend validation rules for high-risk mistakes
15
+ where possible.
16
+