@nextclaw/server 0.18.3 → 0.19.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.
package/dist/index.js CHANGED
@@ -8,10 +8,10 @@ import { lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat, writ
8
8
  import { basename, dirname, extname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
9
9
  import { createHash, randomBytes, randomUUID } from "node:crypto";
10
10
  import * as NextclawCore from "@nextclaw/core";
11
- import { ConfigSchema, DEFAULT_WORKSPACE_PATH, ProviderModelDiscoveryHttpError, buildConfigSchema, createAgentProfile, expandHome, findEffectiveAgentProfile, getDataDir, getPackageVersion, getProviderName, hasSecretRef, isSensitiveConfigPath, loadConfig, mergeExtensionConfigView, normalizeModelThinkingCapability, normalizeProviderModelConfig, probeFeishu, readAgentAvatarContent, removeAgentProfile, resolveEffectiveAgentProfiles, saveConfig, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
11
+ import { ConfigSchema, DEFAULT_WORKSPACE_PATH, McpServerDefinitionSchema, ProviderModelDiscoveryHttpError, buildConfigSchema, createAgentProfile, expandHome, findEffectiveAgentProfile, getDataDir, getPackageVersion, getProviderName, hasSecretRef, isSensitiveConfigPath, loadConfig, mergeExtensionConfigView, normalizeModelThinkingCapability, normalizeProviderModelConfig, probeFeishu, readAgentAvatarContent, removeAgentProfile, resolveEffectiveAgentProfiles, saveConfig, toExtensionConfigView, updateAgentProfile } from "@nextclaw/core";
12
12
  import { homedir, platform } from "node:os";
13
13
  import { findBuiltinProviderByName, listBuiltinProviders } from "@nextclaw/runtime";
14
- import { McpInstalledViewService } from "@nextclaw/mcp";
14
+ import { McpInstalledViewService, McpMutationService, McpRegistryService, normalizeMcpServerName } from "@nextclaw/mcp";
15
15
  import { serveStatic } from "hono/serve-static";
16
16
  //#region src/features/auth/utils/auth-bridge.utils.ts
17
17
  const REMOTE_BRIDGE_DIR = join(getDataDir(), "remote");
@@ -67,7 +67,7 @@ async function readJson(req) {
67
67
  function isRecord$3(value) {
68
68
  return typeof value === "object" && value !== null && !Array.isArray(value);
69
69
  }
70
- function readErrorMessage(value, fallback) {
70
+ function readErrorMessage$1(value, fallback) {
71
71
  if (!isRecord$3(value)) return fallback;
72
72
  const maybeError = value.error;
73
73
  if (!isRecord$3(maybeError)) return fallback;
@@ -390,7 +390,7 @@ function readNcpEventChannel(event) {
390
390
  return readString(metadata.channelId) ?? readString(metadata.channel) ?? parseAgentSessionChannel(readString(message.sessionId) ?? readString(payload.sessionId));
391
391
  }
392
392
  function canStreamAppEventToPrincipal(principal, event) {
393
- if (event.type === "extension.request") {
393
+ if (event.type === "extension.request" || event.type === "extension.host.desktop.event") {
394
394
  const target = readExtensionRequestTarget(event);
395
395
  return hasGrant(principal, "event-stream:extension-requests") && hasScopeValue(principal, "extensionIds", target.extensionId) && hasScopeValue(principal, "extensionGenerations", target.extensionId && target.generation ? `${target.extensionId}:${target.generation}` : null);
396
396
  }
@@ -478,7 +478,8 @@ const ingressKeys = {
478
478
  channelCommandExecute: createTypedKey("extension.channel.command.execute"),
479
479
  runtimeReady: createTypedKey("extension.runtime.ready"),
480
480
  response: createTypedKey("extension.response"),
481
- observationEvent: createTypedKey("extension.observation.event")
481
+ observationEvent: createTypedKey("extension.observation.event"),
482
+ desktopHostInvoke: createTypedKey("extension.host.desktop.invoke")
482
483
  },
483
484
  agentRun: {
484
485
  send: createTypedKey("agent-run.send"),
@@ -1964,6 +1965,131 @@ var AppRoutesController = class {
1964
1965
  extensionCatalog = (c) => c.json(ok(buildExtensionsView(this.options)));
1965
1966
  };
1966
1967
  //#endregion
1968
+ //#region src/app/controllers/capability-access.controller.ts
1969
+ var CapabilityAccessRoutesController = class {
1970
+ constructor(options) {
1971
+ this.options = options;
1972
+ }
1973
+ listGrants = async (c) => this.listDesktopGrants(c);
1974
+ grant = async (c) => {
1975
+ const body = await readJson(c.req.raw);
1976
+ const request = body.ok ? readGrantRequest(body.data) : null;
1977
+ if (!request) return c.json(err("INVALID_CAPABILITY_GRANT", "A valid capability grant request is required."), 400);
1978
+ try {
1979
+ if (request.resource.type !== "desktop.application") return c.json(err("INVALID_CAPABILITY_GRANT", "Generic capability grants must use their resource-specific authorization endpoint."), 400);
1980
+ return c.json(ok(await this.options.getDesktopHost().grantAccess(request)), 201);
1981
+ } catch (error) {
1982
+ return c.json(err("INVALID_CAPABILITY_GRANT", readErrorMessage(error)), 400);
1983
+ }
1984
+ };
1985
+ revoke = async (c) => {
1986
+ const body = await readJson(c.req.raw);
1987
+ const filter = body.ok ? readGrantFilter(body.data) : null;
1988
+ if (!filter || !isExactGrantSelector(filter) || filter.resourceType !== "desktop.application") return c.json(err("INVALID_CAPABILITY_GRANT_FILTER", "An exact Desktop subject, resource type, and target selector is required."), 400);
1989
+ return c.json(ok({ revoked: await this.options.capabilityGrantManager.revoke(filter) }));
1990
+ };
1991
+ getDesktopStatus = async (c) => c.json(ok(await this.options.getDesktopHost().status()));
1992
+ getDesktopPermissions = async (c) => c.json(ok(await this.options.getDesktopHost().getPermissions()));
1993
+ requestDesktopPermissions = async (c) => c.json(ok(await this.options.getDesktopHost().requestPermissions()));
1994
+ openDesktopPermissionSettings = async (c) => c.json(ok(await this.options.getDesktopHost().openPermissionSettings()));
1995
+ listDesktopGrants = async (c) => {
1996
+ const filter = readGrantFilterFromQuery(c);
1997
+ if (filter.resourceType !== void 0 && filter.resourceType !== "desktop.application") return c.json(err("INVALID_CAPABILITY_GRANT_FILTER", "Only Desktop capability grants can be listed from this endpoint."), 400);
1998
+ if (filter.subject && (!filter.subject.type || !filter.subject.id)) return c.json(err("INVALID_CAPABILITY_GRANT_FILTER", "Desktop grant subject filters must include both type and id."), 400);
1999
+ return c.json(ok(await this.options.capabilityGrantManager.list({
2000
+ ...filter,
2001
+ resourceType: "desktop.application"
2002
+ })));
2003
+ };
2004
+ };
2005
+ function isExactGrantSelector(filter) {
2006
+ return Boolean(filter.subject?.type && filter.subject.id && filter.resourceType && filter.access?.length && Object.hasOwn(filter, "target") && filter.target !== void 0);
2007
+ }
2008
+ function readGrantFilterFromQuery(c) {
2009
+ const subjectType = readOptionalString(c.req.query("subjectType"));
2010
+ const subjectId = readOptionalString(c.req.query("subjectId"));
2011
+ const resourceType = readOptionalString(c.req.query("resourceType"));
2012
+ return {
2013
+ ...subjectType || subjectId ? { subject: {
2014
+ ...subjectType ? { type: subjectType } : {},
2015
+ ...subjectId ? { id: subjectId } : {}
2016
+ } } : {},
2017
+ ...resourceType ? { resourceType } : {}
2018
+ };
2019
+ }
2020
+ function readGrantRequest(value) {
2021
+ if (!isRecord$3(value) || !hasExactKeys(value, [
2022
+ "access",
2023
+ "declarationFingerprint",
2024
+ "resource",
2025
+ "subject"
2026
+ ])) return null;
2027
+ const subject = readSubject(value.subject, false);
2028
+ const resource = isRecord$3(value.resource) && hasExactKeys(value.resource, ["target", "type"]) ? value.resource : null;
2029
+ if (!subject || !resource || !readOptionalString(resource.type) || !Array.isArray(value.access) || value.access.length === 0 || !value.access.every((entry) => Boolean(readOptionalString(entry))) || !readOptionalString(value.declarationFingerprint)) return null;
2030
+ return {
2031
+ subject: {
2032
+ type: subject.type,
2033
+ id: subject.id
2034
+ },
2035
+ resource: {
2036
+ type: String(resource.type).trim(),
2037
+ target: resource.target
2038
+ },
2039
+ access: value.access.map((entry) => String(entry).trim()),
2040
+ declarationFingerprint: String(value.declarationFingerprint).trim()
2041
+ };
2042
+ }
2043
+ function readGrantFilter(value) {
2044
+ if (!isRecord$3(value) || !hasExactKeys(value, [
2045
+ "access",
2046
+ "resourceType",
2047
+ "subject",
2048
+ "target"
2049
+ ], true)) return null;
2050
+ const subject = value.subject === void 0 ? void 0 : readSubject(value.subject, true);
2051
+ if (value.subject !== void 0 && !subject) return null;
2052
+ const resourceType = value.resourceType === void 0 ? void 0 : readOptionalString(value.resourceType);
2053
+ if (value.resourceType !== void 0 && !resourceType) return null;
2054
+ const access = value.access === void 0 ? void 0 : Array.isArray(value.access) && value.access.length > 0 && value.access.every((entry) => Boolean(readOptionalString(entry))) ? value.access.map((entry) => String(entry).trim()) : null;
2055
+ if (access === null) return null;
2056
+ return {
2057
+ ...subject ? { subject } : {},
2058
+ ...resourceType ? { resourceType } : {},
2059
+ ...Object.hasOwn(value, "target") ? { target: value.target } : {},
2060
+ ...access ? { access } : {}
2061
+ };
2062
+ }
2063
+ function readSubject(value, partial) {
2064
+ if (!isRecord$3(value) || !hasExactKeys(value, ["id", "type"], partial)) return null;
2065
+ const type = value.type === void 0 ? void 0 : readOptionalString(value.type);
2066
+ const id = value.id === void 0 ? void 0 : readOptionalString(value.id);
2067
+ if (!partial && (!type || !id) || value.type !== void 0 && !type || value.id !== void 0 && !id) return null;
2068
+ return {
2069
+ ...type ? { type } : {},
2070
+ ...id ? { id } : {}
2071
+ };
2072
+ }
2073
+ function hasExactKeys(value, allowed, allowMissing = false) {
2074
+ const keys = Object.keys(value);
2075
+ return keys.every((key) => allowed.includes(key)) && (allowMissing || keys.length === allowed.length);
2076
+ }
2077
+ function readOptionalString(value) {
2078
+ if (typeof value !== "string") return void 0;
2079
+ return value.trim() || void 0;
2080
+ }
2081
+ function readErrorMessage(error) {
2082
+ return error instanceof Error ? error.message : String(error);
2083
+ }
2084
+ //#endregion
2085
+ //#region src/features/feature-controls/controllers/feature-controls.controller.ts
2086
+ var FeatureControlsRoutesController = class {
2087
+ constructor(featureControls) {
2088
+ this.featureControls = featureControls;
2089
+ }
2090
+ get = async (c) => c.json(ok(await this.featureControls.get()));
2091
+ };
2092
+ //#endregion
1967
2093
  //#region src/features/app-packages/controllers/app-packages.controller.ts
1968
2094
  var AppPackagesRoutesController = class {
1969
2095
  constructor(manager) {
@@ -4846,7 +4972,7 @@ async function fetchMarketplaceDataFromBase(params) {
4846
4972
  if (!response.ok) return {
4847
4973
  ok: false,
4848
4974
  status: response.status,
4849
- message: readErrorMessage(payload, `marketplace request failed (${response.status})`)
4975
+ message: readErrorMessage$1(payload, `marketplace request failed (${response.status})`)
4850
4976
  };
4851
4977
  if (!payload || typeof payload !== "object" || !("ok" in payload)) return {
4852
4978
  ok: false,
@@ -4857,7 +4983,7 @@ async function fetchMarketplaceDataFromBase(params) {
4857
4983
  if (!typed.ok) return {
4858
4984
  ok: false,
4859
4985
  status: 502,
4860
- message: readErrorMessage(payload, "marketplace response returned error")
4986
+ message: readErrorMessage$1(payload, "marketplace response returned error")
4861
4987
  };
4862
4988
  return {
4863
4989
  ok: true,
@@ -6038,6 +6164,88 @@ var NcpSessionRoutesController = class {
6038
6164
  };
6039
6165
  };
6040
6166
  //#endregion
6167
+ //#region src/features/mcp/controllers/mcp.controller.ts
6168
+ var McpRoutesController = class {
6169
+ constructor(options) {
6170
+ this.options = options;
6171
+ }
6172
+ testConnection = async (c) => {
6173
+ const parsed = await this.readConnectionRequest(c);
6174
+ if (!parsed.ok) return parsed.response;
6175
+ const config = structuredClone(loadConfig(this.options.configPath));
6176
+ const result = new McpMutationService({
6177
+ getConfig: () => config,
6178
+ saveConfig: () => void 0
6179
+ }).addServer(parsed.value.name, parsed.value.definition);
6180
+ if (!result.changed) return c.json(err("MCP_CONNECTION_INVALID", result.message), 400);
6181
+ const registry = new McpRegistryService({ getConfig: () => config });
6182
+ try {
6183
+ const warm = await registry.warmServer(result.name);
6184
+ return c.json(ok({
6185
+ name: warm.name,
6186
+ transport: parsed.value.definition.transport.type,
6187
+ accessible: warm.ok,
6188
+ toolCount: warm.toolCount,
6189
+ ...warm.error ? { error: warm.error } : {}
6190
+ }));
6191
+ } finally {
6192
+ await registry.close();
6193
+ }
6194
+ };
6195
+ createConnection = async (c) => {
6196
+ const parsed = await this.readConnectionRequest(c);
6197
+ if (!parsed.ok) return parsed.response;
6198
+ const result = new McpMutationService({
6199
+ getConfig: () => loadConfig(this.options.configPath),
6200
+ saveConfig: (config) => saveConfig(config, this.options.configPath)
6201
+ }).addServer(parsed.value.name, parsed.value.definition);
6202
+ if (!result.changed || !result.definition) return c.json(err("MCP_CONNECTION_INVALID", result.message), 400);
6203
+ emitConfigUpdated(this.options, "mcp");
6204
+ await this.options.applyLiveConfigReload?.();
6205
+ return c.json(ok({
6206
+ name: result.name,
6207
+ transport: result.definition.transport.type,
6208
+ message: result.message
6209
+ }));
6210
+ };
6211
+ readConnectionRequest = async (c) => {
6212
+ const body = await readJson(c.req.raw);
6213
+ if (!body.ok || !body.data || typeof body.data !== "object") return {
6214
+ ok: false,
6215
+ response: c.json(err("INVALID_BODY", "invalid json body"), 400)
6216
+ };
6217
+ try {
6218
+ const name = normalizeMcpServerName(body.data.name ?? "");
6219
+ const definition = McpServerDefinitionSchema.parse(body.data.definition);
6220
+ return {
6221
+ ok: true,
6222
+ value: {
6223
+ name,
6224
+ definition: {
6225
+ ...definition,
6226
+ metadata: {
6227
+ ...definition.metadata,
6228
+ source: "manual",
6229
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
6230
+ }
6231
+ }
6232
+ }
6233
+ };
6234
+ } catch (error) {
6235
+ return {
6236
+ ok: false,
6237
+ response: c.json(err("MCP_CONNECTION_INVALID", error instanceof Error ? error.message : String(error)), 400)
6238
+ };
6239
+ }
6240
+ };
6241
+ };
6242
+ //#endregion
6243
+ //#region src/features/mcp/routes/mcp.route.ts
6244
+ function mountMcpRoutes(app, controller) {
6245
+ app.post("/api/mcp/servers/test", controller.testConnection);
6246
+ app.post("/api/mcp/servers", controller.createConnection);
6247
+ }
6248
+ //#endregion
6041
6249
  //#region src/features/remote-access/controllers/remote.controller.ts
6042
6250
  const REMOTE_SERVICE_ACTIONS = new Set([
6043
6251
  "start",
@@ -6748,6 +6956,11 @@ function createUiRouteControllers(options, authService, marketplaceBaseUrls, ser
6748
6956
  const { kernel, panelAppClientSdkScript, remoteAccess, runtimeControl, runtimeUpdate } = options;
6749
6957
  return {
6750
6958
  app: new AppRoutesController(options),
6959
+ capabilityAccess: new CapabilityAccessRoutesController({
6960
+ capabilityGrantManager: kernel.capabilityGrants,
6961
+ getDesktopHost: () => kernel.extensions.getDesktopHost()
6962
+ }),
6963
+ featureControls: new FeatureControlsRoutesController(kernel.featureControls),
6751
6964
  appPackages: new AppPackagesRoutesController(kernel.appPackageManager),
6752
6965
  appData: new AppDataRoutesController(kernel.appDataManager),
6753
6966
  agents: new AgentsRoutesController(options),
@@ -6770,7 +6983,8 @@ function createUiRouteControllers(options, authService, marketplaceBaseUrls, ser
6770
6983
  runtimeControl: runtimeControl ? new RuntimeControlRoutesController(runtimeControl) : null,
6771
6984
  runtimeUpdate: runtimeUpdate ? new RuntimeUpdateRoutesController(runtimeUpdate) : null,
6772
6985
  skillMarketplace: new SkillMarketplaceController(options, marketplaceBaseUrls),
6773
- mcpMarketplace: new McpMarketplaceController(options, marketplaceBaseUrls)
6986
+ mcpMarketplace: new McpMarketplaceController(options, marketplaceBaseUrls),
6987
+ mcp: new McpRoutesController(options)
6774
6988
  };
6775
6989
  }
6776
6990
  function isRecord(value) {
@@ -6874,7 +7088,7 @@ var UiRouteRegistry = class {
6874
7088
  ]]);
6875
7089
  };
6876
7090
  mountResourceRoutes = () => {
6877
- const { appData, appPackages, ncpSession, inboxDeliveries, panelApps, preferences, projects, serviceApps, serverPath, systemObjectReferences } = this.controllers;
7091
+ const { appData, appPackages, capabilityAccess, featureControls, ncpSession, inboxDeliveries, panelApps, preferences, projects, serviceApps, serverPath, systemObjectReferences } = this.controllers;
6878
7092
  this.mountRoutes([
6879
7093
  [
6880
7094
  "get",
@@ -6981,6 +7195,46 @@ var UiRouteRegistry = class {
6981
7195
  "/api/system-object-references/resolve",
6982
7196
  systemObjectReferences.resolve
6983
7197
  ],
7198
+ [
7199
+ "get",
7200
+ "/api/capability-grants",
7201
+ capabilityAccess.listGrants
7202
+ ],
7203
+ [
7204
+ "post",
7205
+ "/api/capability-grants",
7206
+ capabilityAccess.grant
7207
+ ],
7208
+ [
7209
+ "delete",
7210
+ "/api/capability-grants",
7211
+ capabilityAccess.revoke
7212
+ ],
7213
+ [
7214
+ "get",
7215
+ "/api/feature-controls",
7216
+ featureControls.get
7217
+ ],
7218
+ [
7219
+ "get",
7220
+ "/api/desktop-host/status",
7221
+ capabilityAccess.getDesktopStatus
7222
+ ],
7223
+ [
7224
+ "get",
7225
+ "/api/desktop-host/permissions",
7226
+ capabilityAccess.getDesktopPermissions
7227
+ ],
7228
+ [
7229
+ "post",
7230
+ "/api/desktop-host/permissions/request",
7231
+ capabilityAccess.requestDesktopPermissions
7232
+ ],
7233
+ [
7234
+ "post",
7235
+ "/api/desktop-host/permissions/open-settings",
7236
+ capabilityAccess.openDesktopPermissionSettings
7237
+ ],
6984
7238
  [
6985
7239
  "get",
6986
7240
  "/api/app-packages",
@@ -7650,6 +7904,7 @@ var UiRouteRegistry = class {
7650
7904
  skill: this.controllers.skillMarketplace,
7651
7905
  mcp: this.controllers.mcpMarketplace
7652
7906
  });
7907
+ mountMcpRoutes(this.app, this.controllers.mcp);
7653
7908
  };
7654
7909
  };
7655
7910
  function createUiRouter(options, authServiceOverride, internal) {