@toolbox-sdk/core
Version:
JavaScript Base SDK for interacting with the Toolbox service
177 lines • 7.08 kB
JavaScript
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import axios from 'axios';
import { Protocol, isVersionAtLeast, } from '../protocol.js';
export class McpHttpTransportBase {
constructor(baseUrl, session, protocol = Protocol.MCP, clientName, clientVersion) {
this._serverVersion = null;
this._initPromise = null;
const parsed = new URL(baseUrl);
let path = parsed.pathname;
if (path.endsWith('/mcp')) {
path += '/';
}
else if (!path.includes('/mcp/')) {
path = path.replace(/\/+$/, '') + '/mcp/';
}
parsed.pathname = path;
this._mcpBaseUrl = parsed.toString();
this._protocolVersion = protocol;
this._clientName = clientName;
this._clientVersion = clientVersion;
this._manageSession = !session;
this._session = session || axios.create();
}
async ensureInitialized(headers) {
if (!this._initPromise) {
this._initPromise = this.initializeSession(headers);
}
await this._initPromise;
}
get protocolVersion() {
return this._protocolVersion;
}
get baseUrl() {
return this._mcpBaseUrl;
}
appendToolsetPath(toolsetName) {
if (!toolsetName) {
return this._mcpBaseUrl;
}
const parsed = new URL(this._mcpBaseUrl);
parsed.pathname = parsed.pathname.replace(/\/+$/, '') + '/' + toolsetName;
return parsed.toString();
}
convertToolSchema(toolData) {
const data = toolData;
let paramAuth = null;
let invokeAuth = [];
if (data._meta && typeof data._meta === 'object') {
const meta = data._meta;
const is2026OrNewer = isVersionAtLeast(this._protocolVersion, Protocol.MCP_v20260728);
if (is2026OrNewer) {
if (meta['com.google.cloud/authParam'] &&
typeof meta['com.google.cloud/authParam'] === 'object') {
paramAuth = meta['com.google.cloud/authParam'];
}
if (meta['com.google.cloud/authInvoke'] &&
Array.isArray(meta['com.google.cloud/authInvoke'])) {
invokeAuth = meta['com.google.cloud/authInvoke'];
}
}
else {
if (meta['toolbox/authParam'] &&
typeof meta['toolbox/authParam'] === 'object') {
paramAuth = meta['toolbox/authParam'];
}
if (meta['toolbox/authInvoke'] &&
Array.isArray(meta['toolbox/authInvoke'])) {
invokeAuth = meta['toolbox/authInvoke'];
}
}
}
const parameters = [];
const inputSchema = data.inputSchema || {};
const properties = inputSchema.properties || {};
const required = new Set(inputSchema.required || []);
for (const [name, schema] of Object.entries(properties)) {
const typeSchema = this._convertTypeSchema(schema);
let authSources;
if (paramAuth && paramAuth[name]) {
authSources = paramAuth[name];
}
parameters.push({
name,
description: schema.description || '',
required: required.has(name),
authSources,
default: schema.default,
...typeSchema,
});
}
return {
description: data.description || '',
parameters,
authRequired: invokeAuth.length > 0 ? invokeAuth : undefined,
};
}
_convertTypeSchema(schemaData) {
const schema = schemaData;
const paramType = schema.type || 'string';
if (paramType === 'array') {
let itemsSchema;
// MCP strictly requires standard JSON Schema formatting:
// https://modelcontextprotocol.io/specification/2025-11-25/server/tools#tool
// This dictates using `items` for array types (https://json-schema.org/understanding-json-schema/reference/array#items)
// and `additionalProperties` for maps (https://json-schema.org/understanding-json-schema/reference/object#additionalproperties).
if (schema.items !== undefined && schema.items !== null) {
// For third-party compatibility, skip strict typing if 'items' is a list (Draft 7 tuple validation).
// Missing 'items' keys default natively to generic lists (list[Any]).
if (typeof schema.items === 'object' && !Array.isArray(schema.items)) {
itemsSchema = this._convertTypeSchema(schema.items);
}
}
return {
type: 'array',
...(itemsSchema ? { items: itemsSchema } : {}),
};
}
else if (paramType === 'object') {
let additionalProperties;
if (schema.additionalProperties !== undefined &&
schema.additionalProperties !== null &&
typeof schema.additionalProperties === 'object' &&
!Array.isArray(schema.additionalProperties)) {
additionalProperties = {
type: schema.additionalProperties.type,
};
}
else {
additionalProperties = schema.additionalProperties !== false;
}
return {
type: 'object',
additionalProperties,
};
}
else {
return {
type: paramType,
};
}
}
processToolResultContent(content) {
const textContentItems = content
.filter(c => c.type === 'text' && typeof c.text === 'string')
.map(c => c.text);
if (textContentItems.length > 1) {
const allJsonObjects = textContentItems.every(item => {
try {
const parsed = JSON.parse(item);
return (typeof parsed === 'object' &&
parsed !== null &&
!Array.isArray(parsed));
}
catch {
return false;
}
});
if (allJsonObjects) {
return `[${textContentItems.join(',')}]`;
}
}
return textContentItems.join('') || 'null';
}
}
//# sourceMappingURL=transportBase.js.map