diff --git a/package.json b/package.json index 92fea8f7..9eda6dfa 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "@inquirer/prompts": "^7.10.1", "@oclif/core": "^4", "@oclif/multi-stage-output": "^0.8.36", - "@salesforce/agents": "^1.10.3", + "@salesforce/agents": "^1.11.1", "@salesforce/core": "^8.28.3", "@salesforce/kit": "^3.2.6", "@salesforce/sf-plugins-core": "^12.2.6", diff --git a/schemas/agent-mcp-create.json b/schemas/agent-mcp-create.json index a4c5b095..13c30c28 100644 --- a/schemas/agent-mcp-create.json +++ b/schemas/agent-mcp-create.json @@ -124,6 +124,9 @@ }, "status": { "type": "string" + }, + "securityWarning": { + "type": "string" } }, "required": [ diff --git a/schemas/agent-mcp-fetch.json b/schemas/agent-mcp-fetch.json index b99f8e9c..d45e364f 100644 --- a/schemas/agent-mcp-fetch.json +++ b/schemas/agent-mcp-fetch.json @@ -46,6 +46,9 @@ }, "status": { "type": "string" + }, + "securityWarning": { + "type": "string" } }, "required": [ diff --git a/src/commands/agent/mcp/asset/list.ts b/src/commands/agent/mcp/asset/list.ts index f3f870e9..dfb5c217 100644 --- a/src/commands/agent/mcp/asset/list.ts +++ b/src/commands/agent/mcp/asset/list.ts @@ -62,16 +62,20 @@ export default class ApiCatalogMcpServerAssetList extends SfCommand ({ id: a.id, name: a.name, + label: a.label, kind: a.kind, active: a.active, availableAsAgentAction: a.availableAsAgentAction, + description: a.description, })), columns: [ { key: 'id', name: 'ID' }, { key: 'name', name: 'Name' }, + { key: 'label', name: 'Label' }, { key: 'kind', name: 'Kind' }, { key: 'active', name: 'Active' }, { key: 'availableAsAgentAction', name: 'Agent Action' }, + { key: 'description', name: 'Description' }, ], }); } diff --git a/src/commands/agent/mcp/asset/replace.ts b/src/commands/agent/mcp/asset/replace.ts index c9be7ea2..9dc036a0 100644 --- a/src/commands/agent/mcp/asset/replace.ts +++ b/src/commands/agent/mcp/asset/replace.ts @@ -73,9 +73,9 @@ export default class ApiCatalogMcpServerAssetReplace extends SfCommand ({ id: a.id, name: a.name, + label: a.label, kind: a.kind, active: a.active, + availableAsAgentAction: a.availableAsAgentAction, + description: a.description, })), columns: [ { key: 'id', name: 'ID' }, { key: 'name', name: 'Name' }, + { key: 'label', name: 'Label' }, { key: 'kind', name: 'Kind' }, { key: 'active', name: 'Active' }, + { key: 'availableAsAgentAction', name: 'Agent Action' }, + { key: 'description', name: 'Description' }, ], }); } diff --git a/src/commands/agent/mcp/create.ts b/src/commands/agent/mcp/create.ts index 6befd20f..b71ec35b 100644 --- a/src/commands/agent/mcp/create.ts +++ b/src/commands/agent/mcp/create.ts @@ -129,6 +129,28 @@ export default class ApiCatalogMcpServerCreate extends SfCommand 0) { + this.table({ + data: result.assets.map((a) => ({ + name: a.name, + label: a.label, + kind: a.kind, + active: a.active, + availableAsAgentAction: a.availableAsAgentAction, + description: a.description, + securityWarning: a.securityWarning, + })), + columns: [ + { key: 'name', name: 'Name' }, + { key: 'label', name: 'Label' }, + { key: 'kind', name: 'Kind' }, + { key: 'active', name: 'Active' }, + { key: 'availableAsAgentAction', name: 'Agent Action' }, + { key: 'description', name: 'Description' }, + { key: 'securityWarning', name: 'Security Warning' }, + ], + }); + } } return result; diff --git a/src/commands/agent/mcp/fetch.ts b/src/commands/agent/mcp/fetch.ts index 4fa59a0b..f96711a8 100644 --- a/src/commands/agent/mcp/fetch.ts +++ b/src/commands/agent/mcp/fetch.ts @@ -48,30 +48,30 @@ export default class ApiCatalogMcpServerFetch extends SfCommand ({ name: a.name, + label: a.label, kind: a.kind, status: a.status, active: a.active, availableAsAgentAction: a.availableAsAgentAction, + description: a.description, + securityWarning: a.securityWarning, })), columns: [ { key: 'name', name: 'Name' }, + { key: 'label', name: 'Label' }, { key: 'kind', name: 'Kind' }, { key: 'status', name: 'Status' }, { key: 'active', name: 'Active' }, { key: 'availableAsAgentAction', name: 'Agent Action' }, + { key: 'description', name: 'Description' }, + { key: 'securityWarning', name: 'Security Warning' }, ], }); } diff --git a/src/commands/agent/mcp/get.ts b/src/commands/agent/mcp/get.ts index aac6004e..e001926b 100644 --- a/src/commands/agent/mcp/get.ts +++ b/src/commands/agent/mcp/get.ts @@ -48,22 +48,18 @@ export default class ApiCatalogMcpServerGet extends SfCommand ({ + id: s.id, + name: s.name, + label: s.label, + type: s.type, + status: s.status, + authType: s.authorization?.authType, + serverUrl: s.serverUrl, + description: s.description, + })), columns: [ { key: 'id', name: 'ID' }, { key: 'name', name: 'Name' }, { key: 'label', name: 'Label' }, { key: 'type', name: 'Type' }, { key: 'status', name: 'Status' }, + { key: 'authType', name: 'Auth Type' }, { key: 'serverUrl', name: 'Server URL' }, + { key: 'description', name: 'Description' }, ], }); } diff --git a/test/commands/agent/mcp/mcp.test.ts b/test/commands/agent/mcp/mcp.test.ts index 141aa5f3..03472a0f 100644 --- a/test/commands/agent/mcp/mcp.test.ts +++ b/test/commands/agent/mcp/mcp.test.ts @@ -33,9 +33,10 @@ import AgentMcpAssetReplace from '../../../../src/commands/agent/mcp/asset/repla describe('agent mcp commands', () => { const $$ = new TestContext(); + let sfCommandStubs: ReturnType; beforeEach(() => { - stubSfCommandUx($$.SANDBOX); + sfCommandStubs = stubSfCommandUx($$.SANDBOX); }); afterEach(() => { @@ -275,7 +276,14 @@ describe('agent mcp commands', () => { return Promise.resolve({ assets: [] }); }; - await AgentMcpAssetReplace.run(['--target-org', testOrg.username, '--mcp-server-id', '0XS1', '--assets-file', file]); + await AgentMcpAssetReplace.run([ + '--target-org', + testOrg.username, + '--mcp-server-id', + '0XS1', + '--assets-file', + file, + ]); expect(JSON.parse(captured.body as string).assets[0].name).to.equal('tool-b'); }); @@ -338,7 +346,14 @@ describe('agent mcp commands', () => { $$.fakeConnectionRequest = () => Promise.resolve({} as any); try { - await AgentMcpAssetReplace.run(['--target-org', testOrg.username, '--mcp-server-id', '0XS1', '--assets-file', file]); + await AgentMcpAssetReplace.run([ + '--target-org', + testOrg.username, + '--mcp-server-id', + '0XS1', + '--assets-file', + file, + ]); expect.fail('should have thrown'); } catch (err) { expect((err as { name: string }).name).to.equal('InvalidShape'); @@ -407,4 +422,94 @@ describe('agent mcp commands', () => { expect(captured.url).to.match(/\/mcp-servers\/0XS1\/assets$/); expect(result.assets[0].name).to.equal('McpTool__add'); }); + + it('asset list renders the tool description in the human-readable table', async () => { + const testOrg = new MockTestOrgData(); + await $$.stubAuths(testOrg); + $$.fakeConnectionRequest = () => + Promise.resolve({ + assets: [{ id: '1', name: 'McpTool__add', kind: 'MCP_TOOL', active: true, description: 'Add two numbers' }], + }); + + await AgentMcpAssetList.run(['--target-org', testOrg.username, '--mcp-server-id', '0XS1']); + + // The table must expose a Description column so a user can review a tool before approving it. + const tableArg = sfCommandStubs.table.firstCall.firstArg as { + data: Array>; + columns: Array<{ key: string; name: string }>; + }; + expect(tableArg.columns.map((c) => c.key)).to.include('description'); + expect(tableArg.data[0].description).to.equal('Add two numbers'); + }); + + it('create renders the tool description and security warning in the discovered-asset table', async () => { + const testOrg = new MockTestOrgData(); + await $$.stubAuths(testOrg); + // The auto-fetch on create returns the same live tool metadata as /fetch, + // including securityWarning — so the discovered-asset table must surface it. + $$.fakeConnectionRequest = () => + Promise.resolve({ + server: { id: '0XS1', name: 'my-server', type: 'EXTERNAL', status: 'ACTIVE' }, + assets: [ + { + name: 'McpTool__add', + kind: 'MCP_TOOL', + active: false, + description: 'Add two numbers', + securityWarning: 'Tool description contains text in multiple languages or scripts.', + }, + ], + }); + + await AgentMcpCreate.run([ + '--target-org', + testOrg.username, + '--name', + 'my-server', + '--server-url', + 'https://example.com/mcp', + ]); + + const tableArg = sfCommandStubs.table.firstCall.firstArg as { + data: Array>; + columns: Array<{ key: string; name: string }>; + }; + const keys = tableArg.columns.map((c) => c.key); + expect(keys).to.include('description'); + expect(keys).to.include('securityWarning'); + expect(tableArg.data[0].description).to.equal('Add two numbers'); + expect(tableArg.data[0].securityWarning).to.equal( + 'Tool description contains text in multiple languages or scripts.' + ); + }); + + it('fetch renders the tool description and security warning in the human-readable table', async () => { + const testOrg = new MockTestOrgData(); + await $$.stubAuths(testOrg); + // securityWarning is only populated by the live /fetch introspection. + $$.fakeConnectionRequest = () => + Promise.resolve({ + assets: [ + { + name: 'McpTool__add', + kind: 'MCP_TOOL', + status: 'IN_SYNC', + description: 'Add two numbers', + securityWarning: 'Tool description was modified since last approval.', + }, + ], + }); + + await AgentMcpFetch.run(['--target-org', testOrg.username, '--mcp-server-id', '0XS1']); + + const tableArg = sfCommandStubs.table.firstCall.firstArg as { + data: Array>; + columns: Array<{ key: string; name: string }>; + }; + const keys = tableArg.columns.map((c) => c.key); + expect(keys).to.include('description'); + expect(keys).to.include('securityWarning'); + expect(tableArg.data[0].description).to.equal('Add two numbers'); + expect(tableArg.data[0].securityWarning).to.equal('Tool description was modified since last approval.'); + }); }); diff --git a/yarn.lock b/yarn.lock index 8abfa1aa..11a71968 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1214,18 +1214,6 @@ "@jsonjoy.com/buffers" "^1.0.0" "@jsonjoy.com/codegen" "^1.0.0" -"@mswjs/interceptors@^0.41.0": - version "0.41.9" - resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.41.9.tgz#9d90bbd60d1ddc30dbcbb827a9bb2e470493530d" - integrity sha512-VVPPgHyQ6ShqnrmDWuxjmUIsO9gWyOZFmuOfLd9LfBGQJwZfy0gvv9pbHSJuoFNIYC7ZDX9aoFwowjcdSC4E8w== - dependencies: - "@open-draft/deferred-promise" "^2.2.0" - "@open-draft/logger" "^0.3.0" - "@open-draft/until" "^2.0.0" - is-node-process "^1.2.0" - outvariant "^1.4.3" - strict-event-emitter "^0.5.1" - "@nodable/entities@^2.1.0": version "2.1.1" resolved "https://registry.yarnpkg.com/@nodable/entities/-/entities-2.1.1.tgz#ce41931e9b72606d7f0598d665e46e889285d78a" @@ -1357,24 +1345,6 @@ ansis "^3.17.0" debug "^4.4.3" -"@open-draft/deferred-promise@^2.2.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz#4a822d10f6f0e316be4d67b4d4f8c9a124b073bd" - integrity sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA== - -"@open-draft/logger@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@open-draft/logger/-/logger-0.3.0.tgz#2b3ab1242b360aa0adb28b85f5d7da1c133a0954" - integrity sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ== - dependencies: - is-node-process "^1.2.0" - outvariant "^1.4.0" - -"@open-draft/until@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.1.0.tgz#0acf32f470af2ceaf47f095cdecd40d68666efda" - integrity sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg== - "@pinojs/redact@^0.4.0": version "0.4.0" resolved "https://registry.yarnpkg.com/@pinojs/redact/-/redact-0.4.0.tgz#c3de060dd12640dcc838516aa2a6803cc7b2e9d6" @@ -1411,17 +1381,16 @@ resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8" integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g== -"@salesforce/agents@^1.10.3": - version "1.10.3" - resolved "https://registry.yarnpkg.com/@salesforce/agents/-/agents-1.10.3.tgz#5f78f25624c26ca0679b7654c60808d2e612297f" - integrity sha512-Cww6DizTMcPGi9oK8JrVh2Yt2Mg00l7qUOPRS2U/oRFkXXoMBzzotlA6aj44EuX9PAHR93N1DVUM4WCPTzaSQg== +"@salesforce/agents@^1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@salesforce/agents/-/agents-1.11.1.tgz#eecc4d84169e13562fd0f7337c43fdebffe4b0ff" + integrity sha512-9sn4AnP1ElNct72aSFsilmo3DMT7xpeFwkisBopJDJQj64qTOhz9isJxCAZecGYkIZBx2wXr+S9At0R24utUyA== dependencies: "@salesforce/core" "^8.31.0" "@salesforce/kit" "^3.2.6" "@salesforce/source-deploy-retrieve" "^12.36.2" "@salesforce/types" "^1.7.3" fast-xml-parser "^5.8.0" - nock "^14.0.15" yaml "^2.8.4" "@salesforce/cli-plugins-testkit@^5.3.41": @@ -5038,11 +5007,6 @@ is-negative-zero@^2.0.3: resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.3.tgz#ced903a027aca6381b777a5743069d7376a49747" integrity sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw== -is-node-process@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.2.0.tgz#ea02a1b90ddb3934a19aea414e88edef7e11d134" - integrity sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw== - is-number-object@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.1.1.tgz#144b21e95a1bc148205dcc2814a9134ec41b2541" @@ -5391,11 +5355,6 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stringify-safe@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - json5@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -6124,15 +6083,6 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" -nock@^14.0.15: - version "14.0.15" - resolved "https://registry.yarnpkg.com/nock/-/nock-14.0.15.tgz#23f9978fb20d8b3607dc263f4978cb89648f550b" - integrity sha512-S0a47C9pLvcYx/Ugf0H30BVBEcUgMMBDk9VJIDlJ8XGrfH2QDUD4Tgdp45qDIiHttokBG+IbsOtsvIjGR/j3bg== - dependencies: - "@mswjs/interceptors" "^0.41.0" - json-stringify-safe "^5.0.1" - propagate "^2.0.0" - node-exports-info@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/node-exports-info/-/node-exports-info-1.6.0.tgz#1aedafb01a966059c9a5e791a94a94d93f5c2a13" @@ -6387,11 +6337,6 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" -outvariant@^1.4.0, outvariant@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.3.tgz#221c1bfc093e8fec7075497e7799fdbf43d14873" - integrity sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA== - own-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358" @@ -6763,11 +6708,6 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" -propagate@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" - integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== - proper-lockfile@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" @@ -7603,11 +7543,6 @@ stop-iteration-iterator@^1.1.0: es-errors "^1.3.0" internal-slot "^1.1.0" -strict-event-emitter@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz#1602ece81c51574ca39c6815e09f1a3e8550bd93" - integrity sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ== - "string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"