Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/desktop/src/main/runtime-host-boot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ import {
openRuntimeHostPeerEndpointOwner,
type RuntimeHostPeerEndpointOwner,
} from '@maka/runtime-host/peer-reachability';
import { clientCapabilityEntityId, type WorkspaceTarget } from "@maka/runtime-host/protocol";
import { clientCapabilityEntityId } from "@maka/runtime-host/capability-entity-id";
import { type WorkspaceTarget } from "@maka/runtime-host/protocol";
import { runtimeHostProfileUsesHostWorkspace } from "@maka/runtime-host/profile-kind";
import { createCredentialMcpOAuthStorage, McpClientManager } from "@maka/mcp";
import { createWorkBoardStore } from "@maka/storage/work-board-store";
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/main/runtime-host-native-capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ import {
CLIENT_CAPABILITY_MAX_OFFERS,
CLIENT_CAPABILITY_MAX_TOOLS,
CLIENT_CAPABILITY_MAX_TOOLS_PER_OFFER,
clientCapabilityEntityId,
decodeClientCapabilityReplaceInput,
decodeClientCapabilityToolDescriptor,
type ClientCapabilityCallFrame,
Expand All @@ -42,6 +41,7 @@ import {
type ClientCapabilityServiceOffer,
type ClientCapabilityToolDescriptor,
} from "@maka/runtime-host/protocol";
import { clientCapabilityEntityId } from "@maka/runtime-host/capability-entity-id";
import { toJSONSchema, z } from "zod";
import { withBrowserOriginAdmission } from './browser/browser-origin-admission.js';
import type { DesktopTargetScope } from '../shared/runtime-host-identity.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/mcp-capability-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import type { ClientCapabilityProvider } from '@maka/runtime-host/client';
import {
CLIENT_CAPABILITY_MAX_TOOLS,
CLIENT_CAPABILITY_MAX_TOOLS_PER_OFFER,
clientCapabilityEntityId,
decodeClientCapabilityReplaceInput,
type ClientCapabilityCallResult,
type ClientCapabilityOffer,
} from '@maka/runtime-host/protocol';
import { clientCapabilityEntityId } from '@maka/runtime-host/capability-entity-id';
import type { McpCallResult, McpToolDescriptor } from '@maka/core/mcp';

const CAPABILITY_VERSION = '0';
Expand Down
1 change: 1 addition & 0 deletions packages/runtime-host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"./operator": "./dist/operator/index.js",
"./operator/update-package-evidence": "./dist/operator/update-package-evidence.js",
"./profile-kind": "./dist/profile-kind.js",
"./capability-entity-id": "./dist/capability-entity-id.js",
"./execution-candidate-main": "./dist/execution-candidate-main.js",
"./server": "./dist/server/index.js",
"./test-only/client-capability-host": "./dist/test-only/client-capability-host.js",
Expand Down
50 changes: 50 additions & 0 deletions packages/runtime-host/src/__tests__/capability-entity-id.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 assert from 'node:assert/strict';
import { test } from 'node:test';
import { clientCapabilityEntityId } from '../capability-entity-id.js';

test('passes an already wire-safe identity through unchanged', () => {
assert.equal(clientCapabilityEntityId('my-server-01'), 'my-server-01');
assert.equal(clientCapabilityEntityId('my_server_01'), 'my_server_01');
});

test('normalizes spaces and punctuation into a readable label plus digest', () => {
const id = clientCapabilityEntityId('My Server #01');
assert.match(id, /^My_Server_01_[0-9a-f]{24}$/u);
});

test('truncates an over-long identity to a bounded label with a digest', () => {
const value = 'x'.repeat(300);
const id = clientCapabilityEntityId(value);
assert.ok(id.length <= 128, `expected <=128, got ${id.length}`);
assert.match(id, /_[0-9a-f]{24}$/u);
});

test('distinct over-long values produce distinct digests', () => {
const a = clientCapabilityEntityId('a'.repeat(300));
const b = clientCapabilityEntityId('b'.repeat(300));
assert.notEqual(a, b);
});

test('honors an explicit max length', () => {
const id = clientCapabilityEntityId('server name here', 40);
assert.ok(id.length <= 40, `expected <=40, got ${id.length}`);
});
40 changes: 40 additions & 0 deletions packages/runtime-host/src/capability-entity-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/

/**
* Node-only helper for turning an MCP server/tool identity into a wire-safe
* capability entity id.
*
* This lives outside `protocol/` on purpose: `clientCapabilityEntityId` uses
* `node:crypto`, and the protocol barrel is re-exported by the Desktop
* renderer's startup graph. A value import from the barrel evaluates every
* `export *` module in the browser, so a Node builtin anywhere in that
* closure silently breaks `vite dev`. Keeping the hashing helper in its own
* Node-only module — the same shape as `profile-kind` — lets the renderer keep
* importing browser-safe values from the barrel without ever loading this one.
*/

import { createHash } from 'node:crypto';

export function clientCapabilityEntityId(value: string, maxLength = 128): string {
if (/^[A-Za-z0-9_-]+$/u.test(value) && value.length <= maxLength) return value;
const label = value.replace(/[^A-Za-z0-9_-]+/gu, '_').slice(0, maxLength - 25) || 'mcp';
const digest = createHash('sha256').update(value).digest('hex').slice(0, 24);
return `${label}_${digest}`;
}
13 changes: 0 additions & 13 deletions packages/runtime-host/src/protocol/client-capability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
* under the License.
*/

import { createHash } from 'node:crypto';
import { TOOL_ACTIVITY_KINDS, type ToolActivityKind } from '@maka/core/events';
import {
decodeInteractionAnswer,
Expand Down Expand Up @@ -85,18 +84,6 @@ export const CLIENT_CAPABILITY_MAX_OFFERS = 32;
export const CLIENT_CAPABILITY_MAX_SERVICES = 32;
export const CLIENT_CAPABILITY_MAX_TOOLS_PER_OFFER = 64;

/**
* Normalize an arbitrary Client Capability identity (an MCP server id or tool
* name from user configuration) into a wire-safe entity id: identities that
* already fit pass through unchanged, anything else becomes a readable label
* plus a collision-proof digest of the original value.
*/
export function clientCapabilityEntityId(value: string, maxLength = 128): string {
if (/^[A-Za-z0-9_-]+$/u.test(value) && value.length <= maxLength) return value;
const label = value.replace(/[^A-Za-z0-9_-]+/gu, '_').slice(0, maxLength - 25) || 'mcp';
const digest = createHash('sha256').update(value).digest('hex').slice(0, 24);
return `${label}_${digest}`;
}
export const CLIENT_CAPABILITY_MAX_TOOLS = 256;
export const CLIENT_CAPABILITY_MAX_MANIFEST_BYTES = 56 * 1024;
export const CLIENT_CAPABILITY_MAX_RESULT_BYTES = 24 * 1024 * 1024;
Expand Down