@manfred-kunze-dev/backbone-mcp-server 2.9.0 → 2.10.0-dev.1

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.
@@ -30,9 +30,49 @@ function formatExportDocument(doc) {
30
30
  parts.push({ type: "text", text: `--- ${doc.filename} (json) ---\n${JSON.stringify(doc.jsonContent, null, 2)}` });
31
31
  return parts;
32
32
  }
33
+ const TERMINAL_STATUSES = new Set(["SUCCESS", "PARTIAL_SUCCESS", "FAILURE", "COMPLETED", "FAILED"]);
34
+ /**
35
+ * Poll an async task until terminal status, then fetch and return the result.
36
+ */
37
+ async function waitForTask(client, taskId, pollSeconds = 30) {
38
+ // eslint-disable-next-line no-constant-condition
39
+ while (true) {
40
+ const { data } = await client.GET("/v1/convert/tasks/{taskId}", {
41
+ params: { path: { taskId }, query: { wait: pollSeconds } },
42
+ });
43
+ const status = data?.taskStatus;
44
+ if (status && TERMINAL_STATUSES.has(status.toUpperCase()))
45
+ break;
46
+ }
47
+ const { data } = await client.GET("/v1/convert/tasks/{taskId}/result", {
48
+ params: { path: { taskId } },
49
+ });
50
+ return data;
51
+ }
52
+ /**
53
+ * Format a ConvertResponse into MCP text content parts.
54
+ */
55
+ function formatConvertResponse(result) {
56
+ const parts = [];
57
+ if (result.documents?.length) {
58
+ for (const doc of result.documents) {
59
+ parts.push(...formatExportDocument(doc));
60
+ }
61
+ }
62
+ if (result.errors?.length) {
63
+ parts.push({
64
+ type: "text",
65
+ text: `Errors:\n${result.errors.map((e) => `- ${e.filename ?? "unknown"}: ${e.errorMessage}`).join("\n")}`,
66
+ });
67
+ }
68
+ if (parts.length === 0) {
69
+ parts.push({ type: "text", text: `Conversion completed with status: ${result.status}` });
70
+ }
71
+ return parts;
72
+ }
33
73
  export function register(server, client) {
34
74
  // ── convert_document ────────────────────────────────────────────────────
35
- server.tool("backbone_convert_document", "Convert documents (PDF, DOCX, XLSX, images, etc.) to Markdown, text, HTML, or JSON. Accepts URLs, base64 data, or local file paths. Local files are automatically read and base64-encoded. Use this to convert binary files before passing them to backbone_create_extraction. Pipeline options: 'fast' (default, fast text extraction), 'ocr' (OCR with layout analysis), or 'vlm' (vision language model for image-heavy/complex layouts). Use async mode for large documents.", {
75
+ server.tool("backbone_convert_document", "Convert documents (PDF, DOCX, XLSX, images, etc.) to Markdown, text, HTML, or JSON. Accepts URLs, base64 data, or local file paths. Local files are automatically read and base64-encoded. Use this to convert binary files before passing them to backbone_create_extraction. Pipeline options: 'fast' (default, fast text extraction), 'ocr' (OCR with layout analysis), or 'vlm' (vision language model for image-heavy/complex layouts). Set async=true with waitForCompletion=true for long-running pipelines like VLM — this submits asynchronously and automatically polls until the result is ready.", {
36
76
  sources: z
37
77
  .array(z.object({
38
78
  type: z.enum(["url", "base64", "file"]).describe("Source type: 'url' for HTTP URLs, 'base64' for base64-encoded data, 'file' for local file paths"),
@@ -64,8 +104,13 @@ export function register(server, client) {
64
104
  .boolean()
65
105
  .optional()
66
106
  .default(false)
67
- .describe("If true, submit as async task and return task ID for polling"),
68
- }, async ({ sources, pipeline, options: pipelineOptions, async: isAsync }) => {
107
+ .describe("If true, submit as async task. Combine with waitForCompletion=true to get the final result in a single call."),
108
+ waitForCompletion: z
109
+ .boolean()
110
+ .optional()
111
+ .default(false)
112
+ .describe("When async=true, automatically poll until done and return the final result instead of a task ID. Recommended for VLM pipeline."),
113
+ }, async ({ sources, pipeline, options: pipelineOptions, async: isAsync, waitForCompletion }) => {
69
114
  try {
70
115
  const apiSources = [];
71
116
  for (const src of sources) {
@@ -122,36 +167,17 @@ export function register(server, client) {
122
167
  };
123
168
  if (isAsync) {
124
169
  const { data } = await client.POST("/v1/convert/source/async", { body });
170
+ const taskId = data?.taskId;
171
+ if (waitForCompletion && taskId) {
172
+ const result = await waitForTask(client, taskId);
173
+ return { content: formatConvertResponse(result) };
174
+ }
125
175
  return {
126
- content: [
127
- {
128
- type: "text",
129
- text: JSON.stringify(data, null, 2),
130
- },
131
- ],
176
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
132
177
  };
133
178
  }
134
179
  const { data } = await client.POST("/v1/convert/source", { body });
135
- const convertResult = data;
136
- const parts = [];
137
- if (convertResult.documents?.length) {
138
- for (const doc of convertResult.documents) {
139
- parts.push(...formatExportDocument(doc));
140
- }
141
- }
142
- if (convertResult.errors?.length) {
143
- parts.push({
144
- type: "text",
145
- text: `Errors:\n${convertResult.errors.map((e) => `- ${e.filename ?? "unknown"}: ${e.errorMessage}`).join("\n")}`,
146
- });
147
- }
148
- if (parts.length === 0) {
149
- parts.push({
150
- type: "text",
151
- text: `Conversion completed with status: ${convertResult.status}`,
152
- });
153
- }
154
- return { content: parts };
180
+ return { content: formatConvertResponse(data) };
155
181
  }
156
182
  catch (error) {
157
183
  return {
@@ -194,26 +220,7 @@ export function register(server, client) {
194
220
  const { data } = await client.GET("/v1/convert/tasks/{taskId}/result", {
195
221
  params: { path: { taskId } },
196
222
  });
197
- const result = data;
198
- const parts = [];
199
- if (result.documents?.length) {
200
- for (const doc of result.documents) {
201
- parts.push(...formatExportDocument(doc));
202
- }
203
- }
204
- if (result.errors?.length) {
205
- parts.push({
206
- type: "text",
207
- text: `Errors:\n${result.errors.map((e) => `- ${e.filename ?? "unknown"}: ${e.errorMessage}`).join("\n")}`,
208
- });
209
- }
210
- if (parts.length === 0) {
211
- parts.push({
212
- type: "text",
213
- text: `Task result status: ${result.status}`,
214
- });
215
- }
216
- return { content: parts };
223
+ return { content: formatConvertResponse(data) };
217
224
  }
218
225
  catch (error) {
219
226
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@manfred-kunze-dev/backbone-mcp-server",
3
- "version": "2.9.0",
3
+ "version": "2.10.0-dev.1",
4
4
  "description": "MCP server for the Backbone AI platform",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",