@nextclaw/server 0.18.2 → 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,
@@ -5740,6 +5866,8 @@ function buildSessionObservationsView(input) {
5740
5866
  //#region src/features/sessions/controllers/sessions.controller.ts
5741
5867
  const DEFAULT_SESSION_MESSAGE_PAGE_SIZE = 40;
5742
5868
  const MAX_SESSION_MESSAGE_PAGE_SIZE = 200;
5869
+ const DEFAULT_SESSION_LIST_PAGE_SIZE = 100;
5870
+ const MAX_SESSION_LIST_PAGE_SIZE = 200;
5743
5871
  function sessionProjectError(error) {
5744
5872
  switch (error.code) {
5745
5873
  case "PROJECT_PATH_INVALID_TYPE": return {
@@ -5806,13 +5934,36 @@ var NcpSessionRoutesController = class {
5806
5934
  return c.json(ok(payload));
5807
5935
  };
5808
5936
  listSessions = async (c) => {
5809
- const sessions = await this.options.kernel.sessionManager.listSessions({
5810
- limit: readPositiveInt(c.req.query("limit")),
5811
- peerId: c.req.query("peerId")
5937
+ const sessionManager = this.options.kernel.sessionManager;
5938
+ const page = readPositiveInt(c.req.query("page")) ?? 1;
5939
+ const pageSize = Math.min(readPositiveInt(c.req.query("pageSize")) ?? readPositiveInt(c.req.query("limit")) ?? DEFAULT_SESSION_LIST_PAGE_SIZE, MAX_SESSION_LIST_PAGE_SIZE);
5940
+ const rawQuery = c.req.query("query")?.trim();
5941
+ const peerId = c.req.query("peerId")?.trim();
5942
+ if (peerId) {
5943
+ const sessions = await sessionManager.listSessions({
5944
+ limit: pageSize,
5945
+ peerId
5946
+ });
5947
+ const payload = {
5948
+ sessions: sessions.map(this.withRuntimeStatus),
5949
+ total: sessions.length,
5950
+ page: 1,
5951
+ pageSize,
5952
+ hasMore: false
5953
+ };
5954
+ return c.json(ok(payload));
5955
+ }
5956
+ const result = await sessionManager.listSessionPage({
5957
+ page,
5958
+ pageSize,
5959
+ ...rawQuery ? { query: rawQuery } : {}
5812
5960
  });
5813
5961
  const payload = {
5814
- sessions: sessions.map(this.withRuntimeStatus),
5815
- total: sessions.length
5962
+ sessions: result.sessions.map(this.withRuntimeStatus),
5963
+ total: result.total,
5964
+ page,
5965
+ pageSize,
5966
+ hasMore: page * pageSize < result.total
5816
5967
  };
5817
5968
  return c.json(ok(payload));
5818
5969
  };
@@ -6013,6 +6164,88 @@ var NcpSessionRoutesController = class {
6013
6164
  };
6014
6165
  };
6015
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
6016
6249
  //#region src/features/remote-access/controllers/remote.controller.ts
6017
6250
  const REMOTE_SERVICE_ACTIONS = new Set([
6018
6251
  "start",
@@ -6723,6 +6956,11 @@ function createUiRouteControllers(options, authService, marketplaceBaseUrls, ser
6723
6956
  const { kernel, panelAppClientSdkScript, remoteAccess, runtimeControl, runtimeUpdate } = options;
6724
6957
  return {
6725
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),
6726
6964
  appPackages: new AppPackagesRoutesController(kernel.appPackageManager),
6727
6965
  appData: new AppDataRoutesController(kernel.appDataManager),
6728
6966
  agents: new AgentsRoutesController(options),
@@ -6745,7 +6983,8 @@ function createUiRouteControllers(options, authService, marketplaceBaseUrls, ser
6745
6983
  runtimeControl: runtimeControl ? new RuntimeControlRoutesController(runtimeControl) : null,
6746
6984
  runtimeUpdate: runtimeUpdate ? new RuntimeUpdateRoutesController(runtimeUpdate) : null,
6747
6985
  skillMarketplace: new SkillMarketplaceController(options, marketplaceBaseUrls),
6748
- mcpMarketplace: new McpMarketplaceController(options, marketplaceBaseUrls)
6986
+ mcpMarketplace: new McpMarketplaceController(options, marketplaceBaseUrls),
6987
+ mcp: new McpRoutesController(options)
6749
6988
  };
6750
6989
  }
6751
6990
  function isRecord(value) {
@@ -6849,7 +7088,7 @@ var UiRouteRegistry = class {
6849
7088
  ]]);
6850
7089
  };
6851
7090
  mountResourceRoutes = () => {
6852
- 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;
6853
7092
  this.mountRoutes([
6854
7093
  [
6855
7094
  "get",
@@ -6956,6 +7195,46 @@ var UiRouteRegistry = class {
6956
7195
  "/api/system-object-references/resolve",
6957
7196
  systemObjectReferences.resolve
6958
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
+ ],
6959
7238
  [
6960
7239
  "get",
6961
7240
  "/api/app-packages",
@@ -7625,6 +7904,7 @@ var UiRouteRegistry = class {
7625
7904
  skill: this.controllers.skillMarketplace,
7626
7905
  mcp: this.controllers.mcpMarketplace
7627
7906
  });
7907
+ mountMcpRoutes(this.app, this.controllers.mcp);
7628
7908
  };
7629
7909
  };
7630
7910
  function createUiRouter(options, authServiceOverride, internal) {