@2kw/ai-mcp-server 4.0.0-dev.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/LICENSE +19 -0
- package/README.md +123 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.js +46 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.js +22 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +116 -0
- package/dist/mime.d.ts +2 -0
- package/dist/mime.js +56 -0
- package/dist/schema-compat.d.ts +11 -0
- package/dist/schema-compat.js +51 -0
- package/dist/tools/ai-gateway.d.ts +4 -0
- package/dist/tools/ai-gateway.js +107 -0
- package/dist/tools/analytics.d.ts +4 -0
- package/dist/tools/analytics.js +163 -0
- package/dist/tools/billing.d.ts +4 -0
- package/dist/tools/billing.js +76 -0
- package/dist/tools/conversion.d.ts +4 -0
- package/dist/tools/conversion.js +281 -0
- package/dist/tools/datasets.d.ts +4 -0
- package/dist/tools/datasets.js +200 -0
- package/dist/tools/docs.d.ts +4 -0
- package/dist/tools/docs.js +171 -0
- package/dist/tools/evaluators.d.ts +4 -0
- package/dist/tools/evaluators.js +140 -0
- package/dist/tools/experiments.d.ts +4 -0
- package/dist/tools/experiments.js +231 -0
- package/dist/tools/extraction.d.ts +4 -0
- package/dist/tools/extraction.js +245 -0
- package/dist/tools/prompts.d.ts +4 -0
- package/dist/tools/prompts.js +373 -0
- package/dist/tools/providers.d.ts +4 -0
- package/dist/tools/providers.js +148 -0
- package/dist/tools/schema-labels.d.ts +4 -0
- package/dist/tools/schema-labels.js +88 -0
- package/dist/tools/schema-testing.d.ts +4 -0
- package/dist/tools/schema-testing.js +96 -0
- package/dist/tools/schema-versions.d.ts +4 -0
- package/dist/tools/schema-versions.js +127 -0
- package/dist/tools/schemas.d.ts +4 -0
- package/dist/tools/schemas.js +136 -0
- package/dist/tools/scores.d.ts +4 -0
- package/dist/tools/scores.js +43 -0
- package/dist/tools/tracing.d.ts +4 -0
- package/dist/tools/tracing.js +124 -0
- package/dist/tools/transcription.d.ts +4 -0
- package/dist/tools/transcription.js +76 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2025-present Manfred Kunze Dev
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
+
and confidential. No part of the Software may be reproduced, distributed, or
|
|
7
|
+
transmitted in any form or by any means, including photocopying, recording, or
|
|
8
|
+
other electronic or mechanical methods, without the prior written permission of
|
|
9
|
+
the copyright holder.
|
|
10
|
+
|
|
11
|
+
The Software is provided "as is", without warranty of any kind, express or implied,
|
|
12
|
+
including but not limited to the warranties of merchantability, fitness for a
|
|
13
|
+
particular purpose, and noninfringement. In no event shall the authors or copyright
|
|
14
|
+
holders be liable for any claim, damages, or other liability, whether in an action
|
|
15
|
+
of contract, tort, or otherwise, arising from, out of, or in connection with the
|
|
16
|
+
Software or the use or other dealings in the Software.
|
|
17
|
+
|
|
18
|
+
Usage of this Software is permitted only in accordance with the terms and conditions
|
|
19
|
+
set forth by Manfred Kunze Dev.
|
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Backbone MCP Server
|
|
2
|
+
|
|
3
|
+
An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that gives AI coding assistants direct access to the [Backbone AI](https://backbone.manfred-kunze.dev) platform — manage schemas, run extractions, convert documents, and more from your IDE.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @manfred-kunze-dev/backbone-mcp-server
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly with `npx` (no install needed):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @manfred-kunze-dev/backbone-mcp-server
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
1. Create an API key in the Backbone dashboard under **API Keys**
|
|
20
|
+
2. Add the server to your IDE's MCP configuration (see below)
|
|
21
|
+
3. Start asking your AI assistant to manage your Backbone resources
|
|
22
|
+
|
|
23
|
+
## IDE Configuration
|
|
24
|
+
|
|
25
|
+
### Claude Code
|
|
26
|
+
|
|
27
|
+
Add to `.claude/settings.local.json` in your project root:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"backbone": {
|
|
33
|
+
"command": "npx",
|
|
34
|
+
"args": ["@manfred-kunze-dev/backbone-mcp-server"],
|
|
35
|
+
"env": {
|
|
36
|
+
"BACKBONE_API_KEY": "sk_your_api_key"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Cursor
|
|
44
|
+
|
|
45
|
+
Add to `.cursor/mcp.json` in your project root:
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"backbone": {
|
|
51
|
+
"command": "npx",
|
|
52
|
+
"args": ["@manfred-kunze-dev/backbone-mcp-server"],
|
|
53
|
+
"env": {
|
|
54
|
+
"BACKBONE_API_KEY": "sk_your_api_key"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Windsurf
|
|
62
|
+
|
|
63
|
+
Add to `.windsurf/mcp.json` in your project root:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"backbone": {
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": ["@manfred-kunze-dev/backbone-mcp-server"],
|
|
71
|
+
"env": {
|
|
72
|
+
"BACKBONE_API_KEY": "sk_your_api_key"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### OpenAI Codex CLI
|
|
80
|
+
|
|
81
|
+
Add to `~/.codex/config.json`:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"mcpServers": {
|
|
86
|
+
"backbone": {
|
|
87
|
+
"command": "npx",
|
|
88
|
+
"args": ["@manfred-kunze-dev/backbone-mcp-server"],
|
|
89
|
+
"env": {
|
|
90
|
+
"BACKBONE_API_KEY": "sk_your_api_key"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Works with any MCP-compatible client using the same configuration pattern.
|
|
98
|
+
|
|
99
|
+
## Environment Variables
|
|
100
|
+
|
|
101
|
+
| Variable | Required | Default | Description |
|
|
102
|
+
|----------|----------|---------|-------------|
|
|
103
|
+
| `BACKBONE_API_KEY` | Yes | — | API key for authenticating with the Backbone backend |
|
|
104
|
+
| `BACKBONE_BASE_URL` | No | `https://backbone.manfred-kunze.dev/api` | Base URL of your Backbone instance |
|
|
105
|
+
| `MCP_TRANSPORT` | No | `stdio` | Transport mode: `stdio` or `http` |
|
|
106
|
+
| `MCP_HTTP_PORT` | No | `3100` | Port for HTTP transport |
|
|
107
|
+
|
|
108
|
+
## Available Tools
|
|
109
|
+
|
|
110
|
+
| Category | Tools |
|
|
111
|
+
|----------|-------|
|
|
112
|
+
| **Schemas** | CRUD operations, version management, validation, testing |
|
|
113
|
+
| **Extractions** | Create extractions (sync/async), estimate tokens, re-run |
|
|
114
|
+
| **Document Conversion** | Convert PDFs, DOCX, images to Markdown/text/HTML/JSON |
|
|
115
|
+
| **Transcription** | Transcribe audio files (flac, mp3, mp4, ogg, wav, webm) |
|
|
116
|
+
| **AI Gateway** | Chat completions, list available models |
|
|
117
|
+
| **API Docs** | Browse API documentation by section |
|
|
118
|
+
|
|
119
|
+
For full tool documentation, see the [Backbone docs](https://backbone.manfred-kunze.dev).
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
Proprietary — see [LICENSE](./LICENSE) for details.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import createClient from "openapi-fetch";
|
|
2
|
+
import type { paths } from "./generated/openapi.js";
|
|
3
|
+
export type ApiClient = ReturnType<typeof createClient<paths>> & {
|
|
4
|
+
/** Exposed config for raw fetch calls (e.g. multipart uploads). */
|
|
5
|
+
_config: {
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Create a typed openapi-fetch client with Bearer auth and error middleware.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createApiClient(baseUrl: string, apiKey: string): ApiClient;
|
|
14
|
+
//# sourceMappingURL=client.d.ts.map
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import createClient from "openapi-fetch";
|
|
2
|
+
import { BackboneApiError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Error-handling middleware: intercepts non-ok responses and throws BackboneApiError.
|
|
5
|
+
*/
|
|
6
|
+
const errorMiddleware = {
|
|
7
|
+
async onResponse({ response }) {
|
|
8
|
+
if (response.ok)
|
|
9
|
+
return undefined;
|
|
10
|
+
let body;
|
|
11
|
+
try {
|
|
12
|
+
body = await response.clone().json();
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
body = {
|
|
16
|
+
error: response.statusText,
|
|
17
|
+
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
18
|
+
status: response.status,
|
|
19
|
+
timestamp: new Date().toISOString(),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
throw new BackboneApiError({
|
|
23
|
+
status: body.status ?? response.status,
|
|
24
|
+
error: body.title ?? body.error ?? response.statusText,
|
|
25
|
+
message: body.detail ?? body.message ?? `HTTP ${response.status}: ${response.statusText}`,
|
|
26
|
+
timestamp: body.timestamp ?? new Date().toISOString(),
|
|
27
|
+
});
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Create a typed openapi-fetch client with Bearer auth and error middleware.
|
|
32
|
+
*/
|
|
33
|
+
export function createApiClient(baseUrl, apiKey) {
|
|
34
|
+
const client = createClient({
|
|
35
|
+
baseUrl: baseUrl.replace(/\/+$/, ""),
|
|
36
|
+
headers: {
|
|
37
|
+
Authorization: `Bearer ${apiKey}`,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
client.use(errorMiddleware);
|
|
41
|
+
// Expose config for raw fetch calls (multipart uploads)
|
|
42
|
+
const enriched = client;
|
|
43
|
+
enriched._config = { baseUrl: baseUrl.replace(/\/+$/, ""), apiKey };
|
|
44
|
+
return enriched;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=client.js.map
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
interface ApiErrorBody {
|
|
2
|
+
error: string;
|
|
3
|
+
message: string;
|
|
4
|
+
status: number;
|
|
5
|
+
timestamp: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class BackboneApiError extends Error {
|
|
8
|
+
readonly status: number;
|
|
9
|
+
readonly errorType: string;
|
|
10
|
+
readonly timestamp: string;
|
|
11
|
+
constructor(apiError: ApiErrorBody);
|
|
12
|
+
}
|
|
13
|
+
export declare function formatErrorForMcp(error: unknown): string;
|
|
14
|
+
export {};
|
|
15
|
+
//# sourceMappingURL=errors.d.ts.map
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export class BackboneApiError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
errorType;
|
|
4
|
+
timestamp;
|
|
5
|
+
constructor(apiError) {
|
|
6
|
+
super(apiError.message);
|
|
7
|
+
this.name = "BackboneApiError";
|
|
8
|
+
this.status = apiError.status;
|
|
9
|
+
this.errorType = apiError.error;
|
|
10
|
+
this.timestamp = apiError.timestamp;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function formatErrorForMcp(error) {
|
|
14
|
+
if (error instanceof BackboneApiError) {
|
|
15
|
+
return `API Error (${error.status}): ${error.message}`;
|
|
16
|
+
}
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
return `Error: ${error.message}`;
|
|
19
|
+
}
|
|
20
|
+
return `Unknown error: ${String(error)}`;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=errors.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
|
+
import { createServer } from "node:http";
|
|
6
|
+
import { createApiClient } from "./client.js";
|
|
7
|
+
import * as conversion from "./tools/conversion.js";
|
|
8
|
+
import * as extraction from "./tools/extraction.js";
|
|
9
|
+
import * as aiGateway from "./tools/ai-gateway.js";
|
|
10
|
+
import * as transcription from "./tools/transcription.js";
|
|
11
|
+
import * as schemas from "./tools/schemas.js";
|
|
12
|
+
import * as schemaLabels from "./tools/schema-labels.js";
|
|
13
|
+
import * as schemaVersions from "./tools/schema-versions.js";
|
|
14
|
+
import * as schemaTesting from "./tools/schema-testing.js";
|
|
15
|
+
import * as datasets from "./tools/datasets.js";
|
|
16
|
+
import * as experiments from "./tools/experiments.js";
|
|
17
|
+
import * as evaluators from "./tools/evaluators.js";
|
|
18
|
+
import * as prompts from "./tools/prompts.js";
|
|
19
|
+
import * as providers from "./tools/providers.js";
|
|
20
|
+
import * as billing from "./tools/billing.js";
|
|
21
|
+
import * as scores from "./tools/scores.js";
|
|
22
|
+
import * as tracing from "./tools/tracing.js";
|
|
23
|
+
import * as analytics from "./tools/analytics.js";
|
|
24
|
+
import * as docs from "./tools/docs.js";
|
|
25
|
+
// Read the canonical KW_* env var; fall back to legacy BACKBONE_* for
|
|
26
|
+
// backwards compatibility. Warn once on stderr when the legacy name supplied
|
|
27
|
+
// the value.
|
|
28
|
+
let legacyEnvWarned = false;
|
|
29
|
+
function readEnv(canonical, legacy) {
|
|
30
|
+
const v = process.env[canonical];
|
|
31
|
+
if (v)
|
|
32
|
+
return v;
|
|
33
|
+
const legacyV = process.env[legacy];
|
|
34
|
+
if (legacyV) {
|
|
35
|
+
if (!legacyEnvWarned) {
|
|
36
|
+
legacyEnvWarned = true;
|
|
37
|
+
console.error(`[deprecation] ${legacy} is read for backwards compatibility. ` +
|
|
38
|
+
`Switch to ${canonical} — the BACKBONE_* names will be removed ` +
|
|
39
|
+
`in a future release.`);
|
|
40
|
+
}
|
|
41
|
+
return legacyV;
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
function requireEnv(canonical, legacy) {
|
|
46
|
+
const v = readEnv(canonical, legacy);
|
|
47
|
+
if (!v) {
|
|
48
|
+
console.error(`Missing required environment variable: ${canonical} (legacy: ${legacy})`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
return v;
|
|
52
|
+
}
|
|
53
|
+
const DEFAULT_BASE_URL = process.env.AI_2KW_DEFAULT_BASE_URL ??
|
|
54
|
+
process.env.BACKBONE_DEFAULT_BASE_URL ??
|
|
55
|
+
"https://backbone.manfred-kunze.dev/api";
|
|
56
|
+
async function main() {
|
|
57
|
+
const apiKey = requireEnv("AI_2KW_API_KEY", "BACKBONE_API_KEY");
|
|
58
|
+
const baseUrl = readEnv("AI_2KW_BASE_URL", "BACKBONE_BASE_URL") ?? DEFAULT_BASE_URL;
|
|
59
|
+
const transport = process.env.MCP_TRANSPORT ?? "stdio";
|
|
60
|
+
const httpPort = parseInt(process.env.MCP_HTTP_PORT ?? "3100", 10);
|
|
61
|
+
const client = createApiClient(baseUrl, apiKey);
|
|
62
|
+
const server = new McpServer({
|
|
63
|
+
name: "backbone",
|
|
64
|
+
version: "1.0.0",
|
|
65
|
+
});
|
|
66
|
+
// Register all tool groups
|
|
67
|
+
conversion.register(server, client);
|
|
68
|
+
extraction.register(server, client);
|
|
69
|
+
aiGateway.register(server, client);
|
|
70
|
+
transcription.register(server, client);
|
|
71
|
+
schemas.register(server, client);
|
|
72
|
+
schemaLabels.register(server, client);
|
|
73
|
+
schemaVersions.register(server, client);
|
|
74
|
+
schemaTesting.register(server, client);
|
|
75
|
+
datasets.register(server, client);
|
|
76
|
+
experiments.register(server, client);
|
|
77
|
+
evaluators.register(server, client);
|
|
78
|
+
prompts.register(server, client);
|
|
79
|
+
providers.register(server, client);
|
|
80
|
+
billing.register(server, client);
|
|
81
|
+
scores.register(server, client);
|
|
82
|
+
tracing.register(server, client);
|
|
83
|
+
analytics.register(server, client);
|
|
84
|
+
docs.register(server, client, baseUrl, apiKey);
|
|
85
|
+
if (transport === "http") {
|
|
86
|
+
const httpTransport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
|
87
|
+
const httpServer = createServer(async (req, res) => {
|
|
88
|
+
const url = new URL(req.url ?? "/", `http://localhost:${httpPort}`);
|
|
89
|
+
if (url.pathname === "/mcp") {
|
|
90
|
+
await httpTransport.handleRequest(req, res);
|
|
91
|
+
}
|
|
92
|
+
else if (url.pathname === "/health") {
|
|
93
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
94
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
res.writeHead(404);
|
|
98
|
+
res.end("Not found");
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
await server.connect(httpTransport);
|
|
102
|
+
httpServer.listen(httpPort, () => {
|
|
103
|
+
console.error(`Backbone MCP server listening on http://localhost:${httpPort}/mcp`);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const stdioTransport = new StdioServerTransport();
|
|
108
|
+
await server.connect(stdioTransport);
|
|
109
|
+
console.error("Backbone MCP server running on stdio");
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
main().catch((error) => {
|
|
113
|
+
console.error("Fatal error:", error);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|
|
116
|
+
//# sourceMappingURL=index.js.map
|
package/dist/mime.d.ts
ADDED
package/dist/mime.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const MIME_TYPES = {
|
|
2
|
+
// Documents
|
|
3
|
+
".pdf": "application/pdf",
|
|
4
|
+
".doc": "application/msword",
|
|
5
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
6
|
+
".xls": "application/vnd.ms-excel",
|
|
7
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
8
|
+
".ppt": "application/vnd.ms-powerpoint",
|
|
9
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
10
|
+
".odt": "application/vnd.oasis.opendocument.text",
|
|
11
|
+
".ods": "application/vnd.oasis.opendocument.spreadsheet",
|
|
12
|
+
".odp": "application/vnd.oasis.opendocument.presentation",
|
|
13
|
+
".rtf": "application/rtf",
|
|
14
|
+
".csv": "text/csv",
|
|
15
|
+
".tsv": "text/tab-separated-values",
|
|
16
|
+
// Text
|
|
17
|
+
".txt": "text/plain",
|
|
18
|
+
".md": "text/markdown",
|
|
19
|
+
".html": "text/html",
|
|
20
|
+
".htm": "text/html",
|
|
21
|
+
".xml": "application/xml",
|
|
22
|
+
".json": "application/json",
|
|
23
|
+
".yaml": "application/x-yaml",
|
|
24
|
+
".yml": "application/x-yaml",
|
|
25
|
+
// Images
|
|
26
|
+
".png": "image/png",
|
|
27
|
+
".jpg": "image/jpeg",
|
|
28
|
+
".jpeg": "image/jpeg",
|
|
29
|
+
".gif": "image/gif",
|
|
30
|
+
".bmp": "image/bmp",
|
|
31
|
+
".tiff": "image/tiff",
|
|
32
|
+
".tif": "image/tiff",
|
|
33
|
+
".webp": "image/webp",
|
|
34
|
+
".svg": "image/svg+xml",
|
|
35
|
+
// CAD
|
|
36
|
+
".dxf": "image/vnd.dxf",
|
|
37
|
+
".geo": "application/vnd.trumpf.geo",
|
|
38
|
+
// Email
|
|
39
|
+
".eml": "message/rfc822",
|
|
40
|
+
".msg": "application/vnd.ms-outlook",
|
|
41
|
+
// Audio
|
|
42
|
+
".flac": "audio/flac",
|
|
43
|
+
".mp3": "audio/mpeg",
|
|
44
|
+
".mp4": "audio/mp4",
|
|
45
|
+
".mpeg": "audio/mpeg",
|
|
46
|
+
".mpga": "audio/mpeg",
|
|
47
|
+
".m4a": "audio/mp4",
|
|
48
|
+
".ogg": "audio/ogg",
|
|
49
|
+
".wav": "audio/wav",
|
|
50
|
+
".webm": "audio/webm",
|
|
51
|
+
};
|
|
52
|
+
export function getMimeType(filename) {
|
|
53
|
+
const ext = filename.slice(filename.lastIndexOf(".")).toLowerCase();
|
|
54
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=mime.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize a JSON Schema to be compatible with OpenAI's structured output requirements.
|
|
3
|
+
*
|
|
4
|
+
* OpenAI requires:
|
|
5
|
+
* - Every object type must have `additionalProperties: false`
|
|
6
|
+
* - Every object must list all properties in `required`
|
|
7
|
+
*
|
|
8
|
+
* This function recursively walks the schema and applies these constraints.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ensureOpenAiCompatible(schema: unknown): unknown;
|
|
11
|
+
//# sourceMappingURL=schema-compat.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize a JSON Schema to be compatible with OpenAI's structured output requirements.
|
|
3
|
+
*
|
|
4
|
+
* OpenAI requires:
|
|
5
|
+
* - Every object type must have `additionalProperties: false`
|
|
6
|
+
* - Every object must list all properties in `required`
|
|
7
|
+
*
|
|
8
|
+
* This function recursively walks the schema and applies these constraints.
|
|
9
|
+
*/
|
|
10
|
+
export function ensureOpenAiCompatible(schema) {
|
|
11
|
+
if (schema === null || schema === undefined || typeof schema !== "object") {
|
|
12
|
+
return schema;
|
|
13
|
+
}
|
|
14
|
+
if (Array.isArray(schema)) {
|
|
15
|
+
return schema.map(ensureOpenAiCompatible);
|
|
16
|
+
}
|
|
17
|
+
const obj = schema;
|
|
18
|
+
const result = {};
|
|
19
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
20
|
+
if (key === "properties" && typeof value === "object" && value !== null) {
|
|
21
|
+
// Recurse into each property definition
|
|
22
|
+
const props = {};
|
|
23
|
+
for (const [propName, propValue] of Object.entries(value)) {
|
|
24
|
+
props[propName] = ensureOpenAiCompatible(propValue);
|
|
25
|
+
}
|
|
26
|
+
result[key] = props;
|
|
27
|
+
}
|
|
28
|
+
else if (key === "items") {
|
|
29
|
+
// Recurse into array items
|
|
30
|
+
result[key] = ensureOpenAiCompatible(value);
|
|
31
|
+
}
|
|
32
|
+
else if (key === "anyOf" || key === "oneOf" || key === "allOf") {
|
|
33
|
+
// Recurse into combinators
|
|
34
|
+
result[key] = Array.isArray(value) ? value.map(ensureOpenAiCompatible) : value;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
result[key] = value;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// If this node is an object type with properties, enforce OpenAI constraints
|
|
41
|
+
if (obj.type === "object" && obj.properties && typeof obj.properties === "object") {
|
|
42
|
+
result.additionalProperties = false;
|
|
43
|
+
// Ensure all properties are in required
|
|
44
|
+
const propNames = Object.keys(obj.properties);
|
|
45
|
+
const existing = Array.isArray(obj.required) ? obj.required : [];
|
|
46
|
+
const merged = [...new Set([...existing, ...propNames])];
|
|
47
|
+
result.required = merged;
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=schema-compat.js.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { formatErrorForMcp } from "../errors.js";
|
|
3
|
+
export function register(server, client) {
|
|
4
|
+
// ── chat ────────────────────────────────────────────────────────────────
|
|
5
|
+
server.tool("2kw_chat", "Send a chat completion request through Backbone's OpenAI-compatible AI gateway. Supports multiple providers (OpenAI, Anthropic, Azure, Vertex AI, etc.). Model format: 'provider/model' (e.g. 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5-20250929'). Always non-streaming.", {
|
|
6
|
+
model: z.string().describe("Model identifier in 'provider/model' format"),
|
|
7
|
+
messages: z
|
|
8
|
+
.array(z.object({
|
|
9
|
+
role: z.enum(["system", "user", "assistant", "tool"]),
|
|
10
|
+
content: z.string().nullable(),
|
|
11
|
+
name: z.string().optional(),
|
|
12
|
+
tool_call_id: z.string().optional(),
|
|
13
|
+
}))
|
|
14
|
+
.describe("Chat messages array"),
|
|
15
|
+
temperature: z.number().min(0).max(2).optional().describe("Sampling temperature (0-2)"),
|
|
16
|
+
max_tokens: z.number().optional().describe("Maximum tokens to generate"),
|
|
17
|
+
top_p: z.number().optional().describe("Top-p (nucleus) sampling"),
|
|
18
|
+
frequency_penalty: z.number().optional(),
|
|
19
|
+
presence_penalty: z.number().optional(),
|
|
20
|
+
stop: z.array(z.string()).optional().describe("Stop sequences"),
|
|
21
|
+
response_format: z
|
|
22
|
+
.object({ type: z.enum(["text", "json_object"]) })
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Response format constraint"),
|
|
25
|
+
tools: z.array(z.unknown()).optional().describe("Tool definitions for function calling"),
|
|
26
|
+
tool_choice: z.unknown().optional().describe("Tool choice: 'auto', 'none', 'required', or object"),
|
|
27
|
+
}, async (params) => {
|
|
28
|
+
try {
|
|
29
|
+
// Build the spec-compliant body, then add non-spec OpenAI-compatible fields
|
|
30
|
+
const body = {
|
|
31
|
+
model: params.model,
|
|
32
|
+
messages: params.messages,
|
|
33
|
+
stream: false,
|
|
34
|
+
...(params.temperature !== undefined && { temperature: params.temperature }),
|
|
35
|
+
...(params.max_tokens !== undefined && { max_tokens: params.max_tokens }),
|
|
36
|
+
...(params.top_p !== undefined && { top_p: params.top_p }),
|
|
37
|
+
...(params.frequency_penalty !== undefined && {
|
|
38
|
+
frequency_penalty: params.frequency_penalty,
|
|
39
|
+
}),
|
|
40
|
+
...(params.presence_penalty !== undefined && {
|
|
41
|
+
presence_penalty: params.presence_penalty,
|
|
42
|
+
}),
|
|
43
|
+
...(params.stop && { stop: params.stop }),
|
|
44
|
+
...(params.response_format && { response_format: params.response_format }),
|
|
45
|
+
...(params.tools && { tools: params.tools }),
|
|
46
|
+
...(params.tool_choice !== undefined && { tool_choice: params.tool_choice }),
|
|
47
|
+
};
|
|
48
|
+
const { data } = await client.POST("/v1/chat/completions", {
|
|
49
|
+
body: body,
|
|
50
|
+
});
|
|
51
|
+
const result = data;
|
|
52
|
+
const parts = [];
|
|
53
|
+
const choices = result.choices;
|
|
54
|
+
if (choices) {
|
|
55
|
+
for (const choice of choices) {
|
|
56
|
+
const msg = choice.message;
|
|
57
|
+
if (msg.content) {
|
|
58
|
+
parts.push({ type: "text", text: msg.content });
|
|
59
|
+
}
|
|
60
|
+
if (msg.tool_calls?.length) {
|
|
61
|
+
parts.push({
|
|
62
|
+
type: "text",
|
|
63
|
+
text: `Tool calls:\n${JSON.stringify(msg.tool_calls, null, 2)}`,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const usage = result.usage;
|
|
69
|
+
if (usage) {
|
|
70
|
+
parts.push({
|
|
71
|
+
type: "text",
|
|
72
|
+
text: `\n[Usage: ${usage.prompt_tokens} prompt + ${usage.completion_tokens} completion = ${usage.total_tokens} total tokens]`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return { content: parts };
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
80
|
+
isError: true,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// ── list_models ─────────────────────────────────────────────────────────
|
|
85
|
+
server.tool("2kw_list_models", "List available AI models and providers accessible through the Backbone AI gateway.", {}, async () => {
|
|
86
|
+
try {
|
|
87
|
+
const { data } = await client.GET("/v1/models");
|
|
88
|
+
const result = data;
|
|
89
|
+
const lines = (result.data ?? []).map((m) => `${m.id} (${m.owned_by})`);
|
|
90
|
+
return {
|
|
91
|
+
content: [
|
|
92
|
+
{
|
|
93
|
+
type: "text",
|
|
94
|
+
text: `Available models:\n${lines.join("\n")}`,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
return {
|
|
101
|
+
content: [{ type: "text", text: formatErrorForMcp(error) }],
|
|
102
|
+
isError: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=ai-gateway.js.map
|