diff --git a/docs/dev/developing-mcp.md b/docs/dev/developing-mcp.md index aeec21d..b54de35 100644 --- a/docs/dev/developing-mcp.md +++ b/docs/dev/developing-mcp.md @@ -53,7 +53,7 @@ Rules: strings, or broad exception swallowing. - Keep schema and contract tests close to tool behavior changes. - Keep discoverable-tool metadata current when adding or changing tool groups, - keywords, mutability, risk, or schema-inspection expectations. + keywords, standard tool annotations, or schema-inspection expectations. - For debugger tools, keep state references opaque and scoped to the debug state that returned them. - Keep UI changes separate from core MCP tool behavior unless the task diff --git a/src/MCP-Tests/MCPHaltingTestTool.class.st b/src/MCP-Tests/MCPHaltingTestTool.class.st index 1b25d48..d2b9731 100644 --- a/src/MCP-Tests/MCPHaltingTestTool.class.st +++ b/src/MCP-Tests/MCPHaltingTestTool.class.st @@ -50,6 +50,12 @@ MCPHaltingTestTool >> executeWithRequest: aRequest [ (Smalltalk globals at: #Halt) signal: 'mcp tool halt test' ] +{ #category : 'metadata' } +MCPHaltingTestTool >> groupName [ + + ^ 'plugins' +] + { #category : 'metadata' } MCPHaltingTestTool >> title [ diff --git a/src/MCP-Tests/MCPSaveImageRecordingServer.class.st b/src/MCP-Tests/MCPSaveImageRecordingServer.class.st index 2c160bf..380f9a1 100644 --- a/src/MCP-Tests/MCPSaveImageRecordingServer.class.st +++ b/src/MCP-Tests/MCPSaveImageRecordingServer.class.st @@ -28,8 +28,7 @@ MCPSaveImageRecordingServer >> didSaveImageSession [ { #category : 'private - calling' } MCPSaveImageRecordingServer >> rpcToolCall: aToolName withParams: someArguments [ - (MCPToolRegistry isStaticTool: aToolName) ifTrue: [ - ^ super rpcToolCall: aToolName withParams: someArguments ]. + (self toolRegistry isStaticToolNamed: aToolName) ifTrue: [ ^ super rpcToolCall: aToolName withParams: someArguments ]. ^ super rpcToolCall: 'tool_call' withParams: { (#toolName -> aToolName). (#arguments -> someArguments) } asDictionary diff --git a/src/MCP-Tests/MCPToolAPITestCase.class.st b/src/MCP-Tests/MCPToolAPITestCase.class.st index 30fe57f..a4036b7 100644 --- a/src/MCP-Tests/MCPToolAPITestCase.class.st +++ b/src/MCP-Tests/MCPToolAPITestCase.class.st @@ -51,11 +51,10 @@ MCPToolAPITestCase >> callRawToolNamed: aToolName withArguments: someArguments [ { #category : 'private - calling' } MCPToolAPITestCase >> callToolNamed: aToolName withArguments: someArguments [ - (MCPToolRegistry isStaticTool: aToolName) ifTrue: [ - ^ self callRawToolNamed: aToolName withArguments: someArguments ]. - ^ self - callDiscoveredToolNamed: aToolName - withArguments: someArguments + | mcp | + mcp := self mcpWithoutObservabilityExport. + (mcp toolRegistry isStaticToolNamed: aToolName) ifTrue: [ ^ self callRawToolNamed: aToolName withArguments: someArguments ]. + ^ self callDiscoveredToolNamed: aToolName withArguments: someArguments ] { #category : 'private - results' } @@ -131,6 +130,12 @@ MCPToolAPITestCase >> summaryFrom: aResult [ ^ (self structuredContentFrom: aResult) at: #summary ] +{ #category : 'support' } +MCPToolAPITestCase >> toolRegistry [ + + ^ self mcpWithoutObservabilityExport toolRegistry +] + { #category : 'private - parsing' } MCPToolAPITestCase >> unvalidatedToolRequestForTool: aTool arguments: someArguments [ diff --git a/src/MCP-Tests/MCPToolChangeHistoryToolsTest.class.st b/src/MCP-Tests/MCPToolChangeHistoryToolsTest.class.st index cd1f080..de3548b 100644 --- a/src/MCP-Tests/MCPToolChangeHistoryToolsTest.class.st +++ b/src/MCP-Tests/MCPToolChangeHistoryToolsTest.class.st @@ -16,29 +16,19 @@ MCPToolChangeHistoryToolsTest >> inputPropertyNamesFor: aTool [ ] { #category : 'tests' } -MCPToolChangeHistoryToolsTest >> testChangeHistoryToolsAreDiscoverableWithExpectedRisk [ +MCPToolChangeHistoryToolsTest >> testChangeHistoryToolsAreDiscoverableWithExpectedMetadata [ | metadataByName result toolNames | - result := self - callToolNamed: 'tool_search' - withArguments: { (#group -> 'history') } asDictionary. - metadataByName := (((self dataFrom: result) at: #tools) collect: [ :each | - (each at: #name) -> each ]) asDictionary. + result := self callToolNamed: 'tool_search' withArguments: { (#group -> 'history') } asDictionary. + metadataByName := (((self dataFrom: result) at: #tools) collect: [ :each | (each at: #name) -> each ]) asDictionary. toolNames := metadataByName keys. - #( 'history_file_list' 'history_entry_list' 'history_entry_apply' - 'history_entry_revert' ) do: [ :toolName | + #( 'history_file_list' 'history_entry_list' 'history_entry_apply' 'history_entry_revert' ) do: [ :toolName | | metadata tool | self assert: (toolNames includes: toolName). - tool := MCPToolRegistry - toolNamed: toolName - ifAbsent: [ self fail: 'Missing tool ' , toolName ]. - self - assert: (MCPToolRegistry exposureForTool: tool) - equals: 'discoverable'. + tool := self toolRegistry toolNamed: toolName ifAbsent: [ self fail: 'Missing tool ' , toolName ]. + self assert: tool defaultExposure equals: 'discoverable'. metadata := metadataByName at: toolName. - self - assert: metadata keys asSet - equals: #( name description ) asSet ] + self assert: metadata keys asSet equals: #( name description ) asSet ] ] { #category : 'tests' } diff --git a/src/MCP-Tests/MCPToolContractsTest.class.st b/src/MCP-Tests/MCPToolContractsTest.class.st index b1a62a6..a19143d 100644 --- a/src/MCP-Tests/MCPToolContractsTest.class.st +++ b/src/MCP-Tests/MCPToolContractsTest.class.st @@ -820,6 +820,15 @@ MCPToolContractsTest >> signalHaltForMCPMessageProcessorTest [ (Smalltalk globals at: #Halt) signal: 'mcp processor halt test' ] +{ #category : 'tests' } +MCPToolContractsTest >> testAllStaticExposurePolicyMakesEveryRegistrationStatic [ + + | registry | + registry := MCPToolRegistry new. + registry exposurePolicy: MCPToolExposurePolicy allStatic. + self assert: registry staticToolNames asSet equals: registry publicToolNames asSet +] + { #category : 'tests' } MCPToolContractsTest >> testAllToolOutputSchemasAdvertiseStructuredSummaryWarningsAndStatus [ @@ -878,102 +887,26 @@ MCPToolContractsTest >> testAllToolsUseParsedRequestsAndCommands [ { #category : 'tests' } MCPToolContractsTest >> testBrowseIconToolGroupsKeepRelatedToolsTogether [ - | orderedTools | - orderedTools := OrderedCollection new. - MCPTool toolGroups do: [ :group | orderedTools addAll: group value ]. - self - assert: orderedTools asSet - equals: MCPTool concreteSubclasses asSet. - self assert: orderedTools asArray equals: { - MCPToolSearchTools. - MCPToolGetTool. - MCPToolCallTool. - MCPToolSearchRepositories. - MCPToolVerifyRepositoryIdentity. - MCPToolListRepositoryChanges. - MCPToolDiscardRepositoryChanges. - MCPToolCreateRepository. - MCPToolAttachRepository. - MCPToolUpdateRepository. - MCPToolAddRepositoryRemote. - MCPToolRemoveRepositoryRemote. - MCPToolUpdateRepositoryRemote. - - MCPToolExportRepository. - MCPToolCommitRepository. - MCPToolFetchRepository. - MCPToolPullRepository. - MCPToolPushRepository. - MCPToolCreateRepositoryBranch. - MCPToolSwitchRepositoryBranch. - MCPToolCheckoutRepositoryBranch. - MCPToolAdoptRepositoryHead. - MCPToolLoadBaseline. - MCPToolLoadRepository. - MCPToolSearchPackages. - MCPToolSearchClasses. - MCPToolGetClass. - MCPToolCreateClass. - MCPToolUpdateClassName. - MCPToolUpdateClassSuperclass. - MCPToolUpdateClassPackage. - MCPToolUpdateClassComment. - MCPToolUpdateClassSlots. - MCPToolUpdateClassTraits. - MCPToolUpdateClassSideTraits. - MCPToolUpdateClassSharedVariables. - MCPToolUpdateClassSharedPools. - MCPToolUpdateClassLayout. - MCPToolAddClassSlot. - MCPToolRemoveClassSlot. - MCPToolUpdateClassSlotName. - MCPToolPullUpClassSlot. - MCPToolPushDownClassSlot. - MCPToolRemoveClasses. - MCPToolSearchMethodMetadata. - MCPToolSearchMethodSource. - MCPToolSearchEquivalentMethods. - MCPToolSearchMethodImplementors. - MCPToolSearchMethodSenders. - MCPToolSearchClassReferences. - MCPToolSearchVariableReferences. - MCPToolGetMethod. - MCPToolCompileMethod. - MCPToolUpdateMethodSelector. - MCPToolUpdateMethodProtocol. - MCPToolRewriteMethods. - MCPToolRemoveMethods. - MCPToolListChangeHistoryFiles. - MCPToolListChangeHistoryEntries. - MCPToolApplyChangeHistoryEntries. - MCPToolRevertChangeHistoryEntries. - MCPToolRunTests. - MCPToolRunTestCoverage. - MCPToolCaptureScreenshot. - MCPToolListDebugSessions. - MCPToolGetDebugSession. - MCPToolRemoveDebugSession. - MCPToolSearchDebugCandidates. - MCPToolAttachDebugCandidate. - MCPToolDebugState. - MCPToolDebugVariables. - MCPToolDebugEvaluate. - MCPToolStepIntoDebugSession. - MCPToolStepOverDebugSession. - MCPToolStepThroughDebugSession. - MCPToolRestartDebugSession. - MCPToolResumeDebugSession. - MCPToolTerminateDebugSession. - MCPToolListDebugBreakpoints. - MCPToolSetDebugBreakpoint. - MCPToolRemoveDebugBreakpoint. - MCPToolEnableDebugBreakpoint. - MCPToolDisableDebugBreakpoint. - MCPToolClearDebugBreakpoints. - MCPToolDebugCapture. - MCPToolDebugTest. - MCPToolUpdateDebugMethod. - MCPToolEvaluate } + | builtInToolClasses groupedToolClasses | + builtInToolClasses := MCPTool concreteSubclasses select: [ :toolClass | toolClass isBuiltIn ]. + groupedToolClasses := OrderedCollection new. + MCPTool toolGroups do: [ :group | + group value do: [ :toolClass | self assert: toolClass new groupName equals: group key ]. + groupedToolClasses addAll: group value ]. + self assert: groupedToolClasses asSet equals: builtInToolClasses asSet +] + +{ #category : 'tests' } +MCPToolContractsTest >> testBuiltInToolsProvideCatalogMetadata [ + + | validExposures | + validExposures := #( 'static' 'discoverable' ). + self toolRegistry publicTools do: [ :tool | + self assert: (MCPTool builtInGroupNames includes: tool groupName). + self assert: tool groupName equals: tool class groupName. + self assert: (validExposures includes: tool defaultExposure). + self assert: (true = tool annotations readOnlyHint or: [ false = tool annotations readOnlyHint ]). + self assert: tool keywords notEmpty ] ] { #category : 'tests' } @@ -1054,6 +987,17 @@ MCPToolContractsTest >> testCaptureScreenshotHasAccurateNameAndSchemas [ self assert: (outputPropertyNames includes: 'mimeType') ] +{ #category : 'tests' } +MCPToolContractsTest >> testCatalogToolsUseOwningRegistry [ + + | registry | + registry := MCPToolRegistry new. + #( 'tool_search' 'tool_get' ) do: [ :toolName | + | tool | + tool := registry toolNamed: toolName ifAbsent: [ self fail ]. + self assert: tool toolRegistry == registry ] +] + { #category : 'tests' } MCPToolContractsTest >> testClassMutationToolsHaveAccurateNamesAndRequiredArguments [ @@ -1320,27 +1264,17 @@ MCPToolContractsTest >> testDebugInputSchemasAdvertiseExpansionBounds [ MCPToolContractsTest >> testDebuggerToolsAreDiscoverableOnly [ | debugNames metadataByName result staticToolNames | - debugNames := #( 'debug_session_list' 'debug_session_get' - 'debug_session_remove' 'debug_candidate_search' - 'debug_candidate_attach' 'debug_state_get' - 'debug_variable_get' 'debug_expression_evaluate' - 'debug_session_step_into' 'debug_session_step_over' - 'debug_session_step_through' - 'debug_session_restart' 'debug_session_resume' - 'debug_session_terminate' 'debug_breakpoint_list' - 'debug_breakpoint_set' 'debug_breakpoint_remove' - 'debug_breakpoint_enable' 'debug_breakpoint_disable' - 'debug_breakpoint_clear' 'debug_capture' - 'debug_test_run' 'debug_method_update' ). - staticToolNames := MCPToolRegistry staticToolNames. + debugNames := #( 'debug_session_list' 'debug_session_get' 'debug_session_remove' 'debug_candidate_search' + 'debug_candidate_attach' 'debug_state_get' 'debug_variable_get' 'debug_expression_evaluate' + 'debug_session_step_into' 'debug_session_step_over' 'debug_session_step_through' 'debug_session_restart' + 'debug_session_resume' 'debug_session_terminate' 'debug_breakpoint_list' 'debug_breakpoint_set' + 'debug_breakpoint_remove' 'debug_breakpoint_enable' 'debug_breakpoint_disable' 'debug_breakpoint_clear' + 'debug_capture' 'debug_test_run' 'debug_method_update' ). + staticToolNames := self toolRegistry staticToolNames. debugNames do: [ :each | self deny: (staticToolNames includes: each) ]. - result := self - callToolNamed: 'tool_search' - withArguments: { (#group -> 'debugging') } asDictionary. - metadataByName := (((self dataFrom: result) at: #tools) collect: [ - :each | (each at: #name) -> each ]) asDictionary. - debugNames do: [ :each | - self assert: (metadataByName includesKey: each) ] + result := self callToolNamed: 'tool_search' withArguments: { (#group -> 'debugging') } asDictionary. + metadataByName := (((self dataFrom: result) at: #tools) collect: [ :each | (each at: #name) -> each ]) asDictionary. + debugNames do: [ :each | self assert: (metadataByName includesKey: each) ] ] { #category : 'tests' } @@ -1401,27 +1335,21 @@ MCPToolContractsTest >> testDiscoveryUsesConfiguredStaticToolNames [ | createContract metadataByName result searchContract server | server := self mcpWithoutObservabilityExport. - server staticToolNames: - #( 'tool_search' 'tool_get' 'tool_call' 'repository_search' ). - result := server - rpcToolCall: 'tool_search' - withParams: { (#query -> 'repository') } asDictionary. - metadataByName := (((self dataFrom: result) at: #tools) collect: [ :each | - (each at: #name) -> each ]) asDictionary. + server staticToolNames: #( 'tool_search' 'tool_get' 'tool_call' 'repository_search' ). + result := server rpcToolCall: 'tool_search' withParams: { (#query -> 'repository') } asDictionary. + metadataByName := (((self dataFrom: result) at: #tools) collect: [ :each | (each at: #name) -> each ]) asDictionary. self assert: (metadataByName includesKey: 'repository_search'). self assert: (metadataByName includesKey: 'repository_create'). - searchContract := (self dataFrom: (server - rpcToolCall: 'tool_get' - withParams: { (#toolName -> 'repository_search') } asDictionary)) at: #tool. - createContract := (self dataFrom: (server - rpcToolCall: 'tool_get' - withParams: { (#toolName -> 'repository_create') } asDictionary)) at: #tool. - self - assert: searchContract keys asSet - equals: #( name description inputSchema ) asSet. - self - assert: createContract keys asSet - equals: #( name description inputSchema ) asSet + searchContract := (self dataFrom: + (server rpcToolCall: 'tool_get' withParams: { (#toolName -> 'repository_search') } asDictionary)) at: #tool. + createContract := (self dataFrom: + (server rpcToolCall: 'tool_get' withParams: { (#toolName -> 'repository_create') } asDictionary)) at: #tool. + self assert: searchContract keys asSet equals: #( name description group exposure annotations keywords inputSchema ) asSet. + self assert: createContract keys asSet equals: #( name description group exposure annotations keywords inputSchema ) asSet. + self assert: (searchContract at: #exposure) equals: 'static'. + self assert: ((searchContract at: #annotations) at: #readOnlyHint). + self assert: (createContract at: #exposure) equals: 'discoverable'. + self deny: ((createContract at: #annotations) at: #readOnlyHint) ] { #category : 'tests - evaluate' } @@ -1501,6 +1429,17 @@ MCPToolContractsTest >> testEvaluateReturnsRuntimeFailureInformation [ self assert: (text includesSubstring: 'Signaler context:') ] +{ #category : 'tests' } +MCPToolContractsTest >> testExposurePolicyOverridesToolDefault [ + + | registry tool | + registry := MCPToolRegistry new. + tool := registry toolNamed: 'method_protocol_update' ifAbsent: [ self fail ]. + registry exposurePolicy: (MCPToolExposurePolicy staticToolNames: (registry staticToolNames copyWith: tool name)). + self assert: tool defaultExposure equals: 'discoverable'. + self assert: (registry isStaticToolNamed: tool name) +] + { #category : 'tests' } MCPToolContractsTest >> testGetClassHasAccurateNameAndRequiredArguments [ @@ -1551,7 +1490,11 @@ MCPToolContractsTest >> testGetToolReturnsSchemaForCatalogTool [ result := self callToolNamed: 'tool_get' withArguments: { (#toolName -> 'repository_create') } asDictionary. contract := (self dataFrom: result) at: #tool. self assert: (contract at: #name) equals: 'repository_create'. - self assert: contract keys asSet equals: #( name description inputSchema ) asSet. + self assert: contract keys asSet equals: #( name description group exposure annotations keywords inputSchema ) asSet. + self assert: (contract at: #group) equals: 'repositories'. + self assert: (contract at: #exposure) equals: 'discoverable'. + self deny: ((contract at: #annotations) at: #readOnlyHint). + self assert: ((contract at: #keywords) includes: 'repo'). inputProperties := ((contract at: #inputSchema) at: #properties) keys. self assert: inputProperties asSet equals: #( 'name' 'location' 'packageNames' 'subdirectory' ) asSet. self assert: ((contract at: #inputSchema) at: #required) asArray equals: #( 'name' 'location' ). @@ -2240,33 +2183,19 @@ MCPToolContractsTest >> testQueryScopeDescriptionsExplainScopeSemantics [ { #category : 'tests' } MCPToolContractsTest >> testRPCAdvertisesStaticToolSurfaceOnly [ - | discoverableToolNames expectedStaticToolNames toolNames | - toolNames := (self mcpWithoutObservabilityExport rpcToolsList at: - #tools) collect: [ :each | each at: #name ]. - expectedStaticToolNames := #( 'tool_search' 'tool_get' 'tool_call' - 'package_search' 'class_search' - 'class_get' 'class_create' - 'method_metadata_search' - 'method_source_search' - 'method_implementor_search' - 'method_sender_search' - 'method_get' 'method_compile' - 'method_selector_update' + | discoverableToolNames expectedStaticToolNames registry toolNames | + registry := self toolRegistry. + toolNames := (self mcpWithoutObservabilityExport rpcToolsList at: #tools) collect: [ :each | each at: #name ]. + expectedStaticToolNames := #( 'tool_search' 'tool_get' 'tool_call' 'package_search' 'class_search' 'class_get' 'class_create' + 'method_metadata_search' 'method_source_search' 'method_implementor_search' + 'method_sender_search' 'method_get' 'method_compile' 'method_selector_update' 'image_evaluate' 'test_run' ). - discoverableToolNames := MCPToolRegistry publicToolNames difference: - MCPToolRegistry staticToolNames. - self - assert: toolNames asSet - equals: MCPToolRegistry staticToolNames asSet. - self - assert: MCPToolRegistry staticToolNames asSet - equals: expectedStaticToolNames asSet. - expectedStaticToolNames do: [ :each | - self assert: (toolNames includes: each) ]. - discoverableToolNames do: [ :each | - self deny: (toolNames includes: each) ]. - self assert: - (MCPToolRegistry publicToolNames includes: 'method_protocol_update'). + discoverableToolNames := registry publicToolNames difference: registry staticToolNames. + self assert: toolNames asSet equals: registry staticToolNames asSet. + self assert: registry staticToolNames asSet equals: expectedStaticToolNames asSet. + expectedStaticToolNames do: [ :each | self assert: (toolNames includes: each) ]. + discoverableToolNames do: [ :each | self deny: (toolNames includes: each) ]. + self assert: (registry publicToolNames includes: 'method_protocol_update'). self deny: (toolNames includes: 'method_protocol_update') ] @@ -2993,18 +2922,11 @@ MCPToolContractsTest >> testSearchToolSchemasAdvertiseGroupedScopeProperties [ MCPToolContractsTest >> testSearchToolsIncludesStaticSurface [ | metadataByName result staticTool | - result := self - callToolNamed: 'tool_search' - withArguments: { (#query -> 'classes') } asDictionary. - metadataByName := (((self dataFrom: result) at: #tools) collect: [ - :each | (each at: #name) -> each ]) asDictionary. + result := self callToolNamed: 'tool_search' withArguments: { (#query -> 'classes') } asDictionary. + metadataByName := (((self dataFrom: result) at: #tools) collect: [ :each | (each at: #name) -> each ]) asDictionary. self assert: (metadataByName includesKey: 'class_search'). - staticTool := MCPToolRegistry - toolNamed: 'class_search' - ifAbsent: [ self fail: 'Missing class_search tool' ]. - self - assert: (MCPToolRegistry exposureForTool: staticTool) - equals: 'static' + staticTool := self toolRegistry toolNamed: 'class_search' ifAbsent: [ self fail: 'Missing class_search tool' ]. + self assert: staticTool defaultExposure equals: 'static' ] { #category : 'tests' } @@ -3013,14 +2935,8 @@ MCPToolContractsTest >> testSearchToolsKeywordsAreSingleSearchTerms [ | evaluateTool searchTool | searchTool := MCPToolSearchTools new. evaluateTool := MCPToolEvaluate new. - self - assert: (MCPToolRegistry keywordsForTool: searchTool) asSet - equals: #( 'tool' 'catalog' 'search' ) asSet. - self - assert: (MCPToolRegistry keywordsForTool: evaluateTool) asSet - equals: - #( 'image' 'evaluate' 'runtime' 'expression' 'smalltalk' - 'code' ) asSet + self assert: searchTool keywords asSet equals: #( 'tool' 'catalog' 'search' ) asSet. + self assert: evaluateTool keywords asSet equals: #( 'image' 'evaluate' 'runtime' 'expression' 'smalltalk' 'code' ) asSet ] { #category : 'tests' } @@ -3228,44 +3144,43 @@ MCPToolContractsTest >> testServerInitializationLeavesIconCacheEmpty [ self assert: MCPStructureIcon cachedRenderedIconCount equals: 0 ] +{ #category : 'tests' } +MCPToolContractsTest >> testServerRegistryOwnsExposurePolicy [ + + | server | + server := self mcpWithoutObservabilityExport. + self assert: server toolExposurePolicy == server toolRegistry exposurePolicy +] + +{ #category : 'tests' } +MCPToolContractsTest >> testSingularSearchTermsUseGenericInflectionRules [ + + | registry | + registry := MCPToolRegistry new. + self assert: (registry singularSearchTermFor: 'classes') equals: 'class'. + self assert: (registry singularSearchTermFor: 'repositories') equals: 'repository'. + self assert: (registry singularSearchTermFor: 'histories') equals: 'history' +] + { #category : 'tests' } MCPToolContractsTest >> testSpecializedClassAndMethodToolsAreDiscoverableOnly [ | discoverableOnlyNames metadataByName | - discoverableOnlyNames := #( 'class_name_update' 'class_superclass_update' - 'class_package_update' - 'class_comment_update' - 'class_slots_update' 'class_traits_update' - 'class_side_traits_update' - 'class_shared_variables_update' - 'class_shared_pools_update' - 'class_layout_update' - 'class_slot_add' 'class_slot_remove' - 'class_slot_name_update' - 'class_slot_pull_up' 'class_slot_push_down' - 'method_equivalent_search' - 'method_class_reference_search' - 'method_variable_reference_search' - 'test_coverage_run' ). + discoverableOnlyNames := #( 'class_name_update' 'class_superclass_update' 'class_package_update' 'class_comment_update' + 'class_slots_update' 'class_traits_update' 'class_side_traits_update' 'class_shared_variables_update' + 'class_shared_pools_update' 'class_layout_update' 'class_slot_add' 'class_slot_remove' + 'class_slot_name_update' 'class_slot_pull_up' 'class_slot_push_down' 'method_equivalent_search' + 'method_class_reference_search' 'method_variable_reference_search' 'test_coverage_run' ). metadataByName := Dictionary new. - (self allToolSearchMetadataForArguments: Dictionary new) do: [ :each | - metadataByName at: (each at: #name) put: each ]. + (self allToolSearchMetadataForArguments: Dictionary new) do: [ :each | metadataByName at: (each at: #name) put: each ]. discoverableOnlyNames do: [ :each | - | metadata tool | - self assert: (MCPToolRegistry publicToolNames includes: each). - self deny: (MCPToolRegistry staticToolNames includes: each). - tool := MCPToolRegistry - toolNamed: each - ifAbsent: [ self fail: 'Missing tool ' , each ]. - self - assert: (MCPToolRegistry exposureForTool: tool) - equals: 'discoverable'. - metadata := metadataByName - at: each - ifAbsent: [ self fail: 'Missing discoverable metadata for ' , each ]. - self - assert: metadata keys asSet - equals: #( name description ) asSet ] + | metadata tool | + self assert: (self toolRegistry publicToolNames includes: each). + self deny: (self toolRegistry staticToolNames includes: each). + tool := self toolRegistry toolNamed: each ifAbsent: [ self fail: 'Missing tool ' , each ]. + self assert: tool defaultExposure equals: 'discoverable'. + metadata := metadataByName at: each ifAbsent: [ self fail: 'Missing discoverable metadata for ' , each ]. + self assert: metadata keys asSet equals: #( name description ) asSet ] ] { #category : 'tests' } @@ -3297,17 +3212,22 @@ MCPToolContractsTest >> testStringArrayItemDescriptionsStayUseful [ ] { #category : 'tests' } -MCPToolContractsTest >> testToolExposurePolicyUsesDefaultsUntilExplicitStaticListProvided [ +MCPToolContractsTest >> testToolAnnotationsSerializeStandardHints [ - | policy | - self assert: - (MCPToolExposurePolicy default isStaticTool: 'tool_search'). - self deny: - (MCPToolExposurePolicy default isStaticTool: 'repository_search'). - policy := MCPToolExposurePolicy staticToolNames: - #( 'repository_search' ). - self deny: (policy isStaticTool: 'tool_search'). - self assert: (policy isStaticTool: 'repository_search') + | annotations | + annotations := MCPStructureToolAnnotations new + title: 'Safe Update'; + readOnlyHint: false; + destructiveHint: false; + idempotentHint: true; + openWorldHint: false; + yourself. + self assert: annotations asJRPCJSON equals: { + (#title -> 'Safe Update'). + (#readOnlyHint -> false). + (#destructiveHint -> false). + (#idempotentHint -> true). + (#openWorldHint -> false) } asDictionary ] { #category : 'tests' } @@ -3365,160 +3285,111 @@ MCPToolContractsTest >> testToolNamesFollowPublicNamingConvention [ { #category : 'tests' } MCPToolContractsTest >> testToolRegistryAcceptsPluginToolRegistration [ - | registration tool | - MCPToolRegistry unregisterToolNamed: MCPHaltingTestTool toolName. - [ - registration := MCPToolRegistry - registerToolClass: MCPHaltingTestTool - groupName: 'plugins' - defaultExposure: 'discoverable'. - tool := MCPToolRegistry - toolNamed: MCPHaltingTestTool toolName - ifAbsent: [ self fail ]. - self assert: tool == registration tool. - self deny: (MCPToolExposurePolicy default isStaticTool: tool name). - self assert: ((MCPToolRegistry publicToolGroups - detect: [ :each | each key = 'plugins' ] - ifNone: [ self fail ]) value includes: MCPHaltingTestTool) ] - ensure: [ - MCPToolRegistry unregisterToolNamed: MCPHaltingTestTool toolName ] + | registration registry tool | + registry := MCPToolRegistry new. + registration := registry registerToolClass: MCPHaltingTestTool. + tool := registry toolNamed: MCPHaltingTestTool toolName ifAbsent: [ self fail ]. + self assert: tool == registration tool. + self deny: (registry isStaticToolNamed: tool name). + self assert: + ((registry publicToolGroups detect: [ :each | each key = 'plugins' ] ifNone: [ self fail ]) value includes: MCPHaltingTestTool) ] { #category : 'tests' } MCPToolContractsTest >> testToolRegistryNamesUseClassSideMetadata [ - | names | - names := MCPToolRegistry publicToolClasses collect: [ :each | - each toolName ]. - self assert: MCPToolRegistry publicToolNames equals: names. + | names registry | + registry := self toolRegistry. + names := registry publicToolClasses collect: [ :each | each toolName ]. + self assert: registry publicToolNames equals: names. self assert: MCPToolSearchClasses toolName equals: 'class_search'. self assert: MCPToolCreateClass toolName equals: 'class_create'. - self - assert: MCPToolUpdateClassName toolName - equals: 'class_name_update'. - self - assert: MCPToolUpdateClassSuperclass toolName - equals: 'class_superclass_update'. - self - assert: MCPToolUpdateClassPackage toolName - equals: 'class_package_update'. - self - assert: MCPToolUpdateClassComment toolName - equals: 'class_comment_update'. - self - assert: MCPToolUpdateClassSlots toolName - equals: 'class_slots_update'. - self - assert: MCPToolUpdateClassTraits toolName - equals: 'class_traits_update'. - self - assert: MCPToolUpdateClassSideTraits toolName - equals: 'class_side_traits_update'. - self - assert: MCPToolUpdateClassSharedVariables toolName - equals: 'class_shared_variables_update'. - self - assert: MCPToolUpdateClassSharedPools toolName - equals: 'class_shared_pools_update'. - self - assert: MCPToolUpdateClassLayout toolName - equals: 'class_layout_update'. + self assert: MCPToolUpdateClassName toolName equals: 'class_name_update'. + self assert: MCPToolUpdateClassSuperclass toolName equals: 'class_superclass_update'. + self assert: MCPToolUpdateClassPackage toolName equals: 'class_package_update'. + self assert: MCPToolUpdateClassComment toolName equals: 'class_comment_update'. + self assert: MCPToolUpdateClassSlots toolName equals: 'class_slots_update'. + self assert: MCPToolUpdateClassTraits toolName equals: 'class_traits_update'. + self assert: MCPToolUpdateClassSideTraits toolName equals: 'class_side_traits_update'. + self assert: MCPToolUpdateClassSharedVariables toolName equals: 'class_shared_variables_update'. + self assert: MCPToolUpdateClassSharedPools toolName equals: 'class_shared_pools_update'. + self assert: MCPToolUpdateClassLayout toolName equals: 'class_layout_update'. self assert: MCPToolAddClassSlot toolName equals: 'class_slot_add'. - self - assert: MCPToolRemoveClassSlot toolName - equals: 'class_slot_remove'. - self - assert: MCPToolUpdateClassSlotName toolName - equals: 'class_slot_name_update'. - self - assert: MCPToolPullUpClassSlot toolName - equals: 'class_slot_pull_up'. - self - assert: MCPToolPushDownClassSlot toolName - equals: 'class_slot_push_down'. + self assert: MCPToolRemoveClassSlot toolName equals: 'class_slot_remove'. + self assert: MCPToolUpdateClassSlotName toolName equals: 'class_slot_name_update'. + self assert: MCPToolPullUpClassSlot toolName equals: 'class_slot_pull_up'. + self assert: MCPToolPushDownClassSlot toolName equals: 'class_slot_push_down'. self assert: MCPToolRunTests toolName equals: 'test_run'. self assert: MCPToolCompileMethod toolName equals: 'method_compile'. - self - assert: MCPToolUpdateMethodSelector toolName - equals: 'method_selector_update'. - self - assert: MCPToolUpdateMethodProtocol toolName - equals: 'method_protocol_update'. - self - assert: MCPToolVerifyRepositoryIdentity toolName - equals: 'repository_identity_verify'. - self - assert: MCPToolListRepositoryChanges toolName - equals: 'repository_change_list'. - self - assert: MCPToolCreateRepository toolName - equals: 'repository_create'. - self - assert: MCPToolAttachRepository toolName - equals: 'repository_attach'. - self - assert: MCPToolUpdateRepository toolName - equals: 'repository_update'. - self - assert: MCPToolExportRepository toolName - equals: 'repository_export'. - self - assert: MCPToolCommitRepository toolName - equals: 'repository_commit'. - self - assert: MCPToolFetchRepository toolName - equals: 'repository_fetch'. + self assert: MCPToolUpdateMethodSelector toolName equals: 'method_selector_update'. + self assert: MCPToolUpdateMethodProtocol toolName equals: 'method_protocol_update'. + self assert: MCPToolVerifyRepositoryIdentity toolName equals: 'repository_identity_verify'. + self assert: MCPToolListRepositoryChanges toolName equals: 'repository_change_list'. + self assert: MCPToolCreateRepository toolName equals: 'repository_create'. + self assert: MCPToolAttachRepository toolName equals: 'repository_attach'. + self assert: MCPToolUpdateRepository toolName equals: 'repository_update'. + self assert: MCPToolExportRepository toolName equals: 'repository_export'. + self assert: MCPToolCommitRepository toolName equals: 'repository_commit'. + self assert: MCPToolFetchRepository toolName equals: 'repository_fetch'. self assert: MCPToolPullRepository toolName equals: 'repository_pull'. self assert: MCPToolPushRepository toolName equals: 'repository_push'. - self - assert: MCPToolCreateRepositoryBranch toolName - equals: 'repository_branch_create'. - self - assert: MCPToolSwitchRepositoryBranch toolName - equals: 'repository_branch_switch'. - self - assert: MCPToolAdoptRepositoryHead toolName - equals: 'repository_head_adopt'. - self - assert: MCPToolListChangeHistoryFiles toolName - equals: 'history_file_list'. - self - assert: MCPToolListChangeHistoryEntries toolName - equals: 'history_entry_list'. - self - assert: MCPToolApplyChangeHistoryEntries toolName - equals: 'history_entry_apply'. - self - assert: MCPToolRevertChangeHistoryEntries toolName - equals: 'history_entry_revert'. + self assert: MCPToolCreateRepositoryBranch toolName equals: 'repository_branch_create'. + self assert: MCPToolSwitchRepositoryBranch toolName equals: 'repository_branch_switch'. + self assert: MCPToolAdoptRepositoryHead toolName equals: 'repository_head_adopt'. + self assert: MCPToolListChangeHistoryFiles toolName equals: 'history_file_list'. + self assert: MCPToolListChangeHistoryEntries toolName equals: 'history_entry_list'. + self assert: MCPToolApplyChangeHistoryEntries toolName equals: 'history_entry_apply'. + self assert: MCPToolRevertChangeHistoryEntries toolName equals: 'history_entry_revert'. self assert: MCPToolDebugState toolName equals: 'debug_state_get'. - self - assert: MCPToolStepOverDebugSession toolName - equals: 'debug_session_step_over'. + self assert: MCPToolStepOverDebugSession toolName equals: 'debug_session_step_over'. self assert: MCPToolDebugCapture toolName equals: 'debug_capture'. - self - assert: MCPToolUpdateDebugMethod toolName - equals: 'debug_method_update' + self assert: MCPToolUpdateDebugMethod toolName equals: 'debug_method_update' +] + +{ #category : 'tests' } +MCPToolContractsTest >> testToolRegistryRegistrationIsInstanceScoped [ + + | firstRegistry secondRegistry | + firstRegistry := MCPToolRegistry new. + secondRegistry := MCPToolRegistry new. + firstRegistry registerToolClass: MCPHaltingTestTool. + self assert: (firstRegistry includesToolNamed: MCPHaltingTestTool toolName). + self deny: (secondRegistry includesToolNamed: MCPHaltingTestTool toolName) ] { #category : 'tests' } MCPToolContractsTest >> testToolRegistryReusesRegisteredToolInstances [ - | first searchResults second | - first := MCPToolRegistry - toolNamed: 'tool_search' - ifAbsent: [ self fail ]. - second := MCPToolRegistry - toolNamed: 'tool_search' - ifAbsent: [ self fail ]. - searchResults := MCPToolRegistry - toolsMatchingQuery: 'tool' - group: nil - usingPolicy: MCPToolExposurePolicy default. + | first registry searchResults second | + registry := MCPToolRegistry new. + first := registry toolNamed: 'tool_search' ifAbsent: [ self fail ]. + second := registry toolNamed: 'tool_search' ifAbsent: [ self fail ]. + searchResults := registry toolsMatchingQuery: 'tool' group: nil. self assert: first == second. - self - assert: (searchResults detect: [ :each | each name = 'tool_search' ]) - == first + self assert: (searchResults detect: [ :each | each name = 'tool_search' ]) == first +] + +{ #category : 'tests' } +MCPToolContractsTest >> testToolRegistryUsesDefaultExposureUntilStaticListProvided [ + + | registry | + registry := MCPToolRegistry new. + self assert: (registry isStaticToolNamed: 'tool_search'). + self deny: (registry isStaticToolNamed: 'repository_search'). + registry staticToolNames: #( 'repository_search' ). + self deny: (registry isStaticToolNamed: 'tool_search'). + self assert: (registry isStaticToolNamed: 'repository_search') +] + +{ #category : 'tests' } +MCPToolContractsTest >> testToolsOwnRegistryMetadata [ + + | tool | + tool := MCPToolEvaluate new. + self assert: tool groupName equals: 'runtime'. + self assert: tool defaultExposure equals: 'static'. + self deny: tool annotations readOnlyHint. + self assert: (tool keywords includes: 'smalltalk') ] { #category : 'support' } diff --git a/src/MCP-Tests/MCPToolMethodLookupTest.class.st b/src/MCP-Tests/MCPToolMethodLookupTest.class.st index 9b2af4a..af04231 100644 --- a/src/MCP-Tests/MCPToolMethodLookupTest.class.st +++ b/src/MCP-Tests/MCPToolMethodLookupTest.class.st @@ -57,39 +57,22 @@ MCPToolMethodLookupTest >> testLookupSchemasRequireDedicatedArguments [ MCPToolMethodLookupTest >> testLookupToolsArePublicStaticReadTools [ | discoverableNames staticNames | - staticNames := #( 'method_source_search' 'method_implementor_search' - 'method_sender_search' ). - discoverableNames := #( 'method_equivalent_search' - 'method_class_reference_search' - 'method_variable_reference_search' ). + staticNames := #( 'method_source_search' 'method_implementor_search' 'method_sender_search' ). + discoverableNames := #( 'method_equivalent_search' 'method_class_reference_search' 'method_variable_reference_search' ). staticNames do: [ :each | | tool | - self assert: (MCPToolRegistry publicToolNames includes: each). - self assert: (MCPToolRegistry staticToolNames includes: each). - tool := MCPToolRegistry - toolNamed: each - ifAbsent: [ self fail: 'Missing tool ' , each ]. - self - assert: (MCPToolRegistry exposureForTool: tool) - equals: 'static'. - self - assert: (MCPToolRegistry mutabilityForTool: tool) - equals: 'read'. - self assert: (MCPToolRegistry riskForTool: tool) equals: 'low' ]. + self assert: (self toolRegistry publicToolNames includes: each). + self assert: (self toolRegistry staticToolNames includes: each). + tool := self toolRegistry toolNamed: each ifAbsent: [ self fail: 'Missing tool ' , each ]. + self assert: tool defaultExposure equals: 'static'. + self assert: tool annotations readOnlyHint ]. discoverableNames do: [ :each | | tool | - self assert: (MCPToolRegistry publicToolNames includes: each). - self deny: (MCPToolRegistry staticToolNames includes: each). - tool := MCPToolRegistry - toolNamed: each - ifAbsent: [ self fail: 'Missing tool ' , each ]. - self - assert: (MCPToolRegistry exposureForTool: tool) - equals: 'discoverable'. - self - assert: (MCPToolRegistry mutabilityForTool: tool) - equals: 'read'. - self assert: (MCPToolRegistry riskForTool: tool) equals: 'low' ] + self assert: (self toolRegistry publicToolNames includes: each). + self deny: (self toolRegistry staticToolNames includes: each). + tool := self toolRegistry toolNamed: each ifAbsent: [ self fail: 'Missing tool ' , each ]. + self assert: tool defaultExposure equals: 'discoverable'. + self assert: tool annotations readOnlyHint ] ] { #category : 'tests' } diff --git a/src/MCP-Tests/MCPToolRepositoryOperationTest.class.st b/src/MCP-Tests/MCPToolRepositoryOperationTest.class.st index c42f2d2..89537dd 100644 --- a/src/MCP-Tests/MCPToolRepositoryOperationTest.class.st +++ b/src/MCP-Tests/MCPToolRepositoryOperationTest.class.st @@ -13,9 +13,7 @@ Class { MCPToolRepositoryOperationTest >> callToolNamed: aToolName with: someArguments [ | request result tool | - tool := MCPToolRegistry - toolNamed: aToolName - ifAbsent: [ Error signal: 'Unknown MCP tool: ' , aToolName ]. + tool := MCPToolRegistry new toolNamed: aToolName ifAbsent: [ Error signal: 'Unknown MCP tool: ' , aToolName ]. request := tool requestFromToolCallArguments: someArguments. result := tool executeWithRequest: request. ^ result asJRPCJSON diff --git a/src/MCP-Tests/MCPToolRepositoryToolsTest.class.st b/src/MCP-Tests/MCPToolRepositoryToolsTest.class.st index 4579c36..9c92157 100644 --- a/src/MCP-Tests/MCPToolRepositoryToolsTest.class.st +++ b/src/MCP-Tests/MCPToolRepositoryToolsTest.class.st @@ -171,28 +171,19 @@ MCPToolRepositoryToolsTest >> repositoryToolSpecifications [ ] { #category : 'tests' } -MCPToolRepositoryToolsTest >> testRepositoryToolsAreDiscoverableWithExpectedRisk [ +MCPToolRepositoryToolsTest >> testRepositoryToolsAreDiscoverableWithExpectedMetadata [ | metadataByName | metadataByName := self metadataByNameForRepositoryTools. - #( 'repository_identity_verify' 'repository_change_list' - 'repository_create' 'repository_attach' 'repository_update' - 'repository_remote_add' 'repository_remote_remove' - 'repository_remote_update' 'repository_export' 'repository_commit' - 'repository_fetch' 'repository_pull' 'repository_push' - 'repository_branch_create' 'repository_branch_switch' + #( 'repository_identity_verify' 'repository_change_list' 'repository_create' 'repository_attach' 'repository_update' + 'repository_remote_add' 'repository_remote_remove' 'repository_remote_update' 'repository_export' 'repository_commit' + 'repository_fetch' 'repository_pull' 'repository_push' 'repository_branch_create' 'repository_branch_switch' 'repository_branch_checkout' 'repository_head_adopt' ) do: [ :toolName | | metadata tool | metadata := metadataByName at: toolName. - tool := MCPToolRegistry - toolNamed: toolName - ifAbsent: [ self fail: 'Missing tool ' , toolName ]. - self - assert: (MCPToolRegistry exposureForTool: tool) - equals: 'discoverable'. - self - assert: metadata keys asSet - equals: #( name description ) asSet ]. + tool := self toolRegistry toolNamed: toolName ifAbsent: [ self fail: 'Missing tool ' , toolName ]. + self assert: tool defaultExposure equals: 'discoverable'. + self assert: metadata keys asSet equals: #( name description ) asSet ]. self assert: (metadataByName includesKey: 'repository_load') ] diff --git a/src/MCP-Tests/MCPToolStructuredOutputTest.class.st b/src/MCP-Tests/MCPToolStructuredOutputTest.class.st index faf7b2e..182445e 100644 --- a/src/MCP-Tests/MCPToolStructuredOutputTest.class.st +++ b/src/MCP-Tests/MCPToolStructuredOutputTest.class.st @@ -654,5 +654,5 @@ MCPToolStructuredOutputTest >> testToolsListAdvertisesPublicToolContracts [ | tools | tools := MCP new rpcToolsList at: #tools. self assert: tools notEmpty. - tools do: [ :tool | self assert: tool keys asSet equals: #( name title description inputSchema ) asSet ] + tools do: [ :tool | self assert: tool keys asSet equals: #( name title description annotations inputSchema ) asSet ] ] diff --git a/src/MCP/MCP.class.st b/src/MCP/MCP.class.st index e1781fc..c9f18c0 100644 --- a/src/MCP/MCP.class.st +++ b/src/MCP/MCP.class.st @@ -1,5 +1,5 @@ " -Top-level MCP server application object. MCP owns the registered tool instances, the HTTP server, and observability state. It exposes the core JSON-RPC MCP handlers, including initialization, tool listing, tool calls, ping, and empty resource listings. Tool calls are delegated to the matching MCPTool after request validation. +Top-level MCP server application object. MCP owns one MCPToolRegistry, the HTTP server, and observability state. The registry owns registered tool instances and their exposure policy. MCP exposes the core JSON-RPC handlers, including initialization, tool listing, tool calls, ping, and empty resource listings. Tool calls are delegated to the matching MCPTool after request validation. " Class { #name : 'MCP', @@ -8,7 +8,7 @@ Class { 'server', 'toolsList', 'monitoring', - 'toolExposurePolicy' + 'toolRegistry' ], #category : 'MCP-Server', #package : 'MCP', @@ -104,35 +104,22 @@ MCP >> ensureServerIsListening [ MCP >> executeTool: tool named: toolName withParams: arguments [ | monitorRecord request result toolResult | - monitorRecord := self monitoring - recordToolCallStart: toolName - input: arguments. + monitorRecord := self monitoring recordToolCallStart: toolName input: arguments. ^ [ request := tool requestFromToolCallArguments: arguments. - result := tool - executeWithRequest: request - usingPolicy: self toolExposurePolicy. + result := tool executeWithRequest: request. toolResult := result asMCPStructureCallToolResult. self saveImageAfterSuccessfulTool: tool result: toolResult. - self monitoring - recordToolCallFinish: monitorRecord - result: toolResult - error: nil. + self monitoring recordToolCallFinish: monitorRecord result: toolResult error: nil. toolResult asJRPCJSON ] on: Exception do: [ :error | | errorResult | error mcpIsJRPCError ifTrue: [ - self monitoring - recordToolCallFinish: monitorRecord - result: nil - error: error. + self monitoring recordToolCallFinish: monitorRecord result: nil error: error. error pass ]. errorResult := tool errorResultFor: error. - self monitoring - recordToolCallFinish: monitorRecord - result: errorResult - error: error. + self monitoring recordToolCallFinish: monitorRecord result: errorResult error: error. errorResult asJRPCJSON ] ] @@ -293,8 +280,7 @@ MCP >> port: aPortNumber [ { #category : 'private - tools' } MCP >> refreshToolsList [ - toolsList := MCPToolRegistry staticToolsDictionaryUsingPolicy: - self toolExposurePolicy. + toolsList := self toolRegistry staticToolsDictionary. ^ toolsList ] @@ -309,26 +295,15 @@ MCP >> restart [ MCP >> rpcCallToolCallWithParams: arguments [ | callRequest callTool request targetTool | - callTool := MCPToolRegistry - toolNamed: MCPToolCallTool toolName - ifAbsent: [ - Error signal: 'tool_call is not registered.' ] - usingPolicy: self toolExposurePolicy. + callTool := self toolRegistry toolNamed: MCPToolCallTool toolName ifAbsent: [ Error signal: 'tool_call is not registered.' ]. ^ [ request := callTool requestFromToolCallArguments: arguments. callRequest := callTool parsedRequestFromToolRequest: request. - callRequest toolName = callTool name ifTrue: [ - Error signal: 'tool_call cannot call itself.' ]. - targetTool := MCPToolRegistry + callRequest toolName = callTool name ifTrue: [ Error signal: 'tool_call cannot call itself.' ]. + targetTool := self toolRegistry toolNamed: callRequest toolName - ifAbsent: [ - Error signal: - 'Unknown MCP tool: ' , callRequest toolName ] - usingPolicy: self toolExposurePolicy. - self - executeTool: targetTool - named: targetTool name - withParams: callRequest arguments ] + ifAbsent: [ Error signal: 'Unknown MCP tool: ' , callRequest toolName ]. + self executeTool: targetTool named: targetTool name withParams: callRequest arguments ] on: Exception do: [ :error | | errorResult | @@ -439,15 +414,14 @@ MCP >> start [ { #category : 'configuration' } MCP >> staticToolNames [ - ^ self toolExposurePolicy staticToolNames + ^ self toolRegistry staticToolNames ] { #category : 'configuration' } MCP >> staticToolNames: aCollection [ - self toolExposurePolicy: - (MCPToolExposurePolicy staticToolNames: aCollection). - ^ self + self toolRegistry staticToolNames: aCollection. + self refreshToolsList ] { #category : 'start' } @@ -471,17 +445,27 @@ MCP >> supportedPharoMajorVersions [ { #category : 'accessing' } MCP >> toolExposurePolicy [ - ^ toolExposurePolicy ifNil: [ - toolExposurePolicy := MCPToolExposurePolicy default ] + ^ self toolRegistry exposurePolicy ] { #category : 'accessing' } MCP >> toolExposurePolicy: aToolExposurePolicy [ - toolExposurePolicy := aToolExposurePolicy ifNil: [ - MCPToolExposurePolicy default ]. - self refreshToolsList. - ^ toolExposurePolicy + self toolRegistry exposurePolicy: (aToolExposurePolicy ifNil: [ MCPToolExposurePolicy default ]). + self refreshToolsList +] + +{ #category : 'accessing' } +MCP >> toolRegistry [ + + ^ toolRegistry ifNil: [ toolRegistry := MCPToolRegistry new ] +] + +{ #category : 'accessing' } +MCP >> toolRegistry: aToolRegistry [ + + toolRegistry := aToolRegistry ifNil: [ MCPToolRegistry new ]. + self refreshToolsList ] { #category : 'accessing' } diff --git a/src/MCP/MCPGetToolCommand.class.st b/src/MCP/MCPGetToolCommand.class.st index e41bbe0..8fa6097 100644 --- a/src/MCP/MCPGetToolCommand.class.st +++ b/src/MCP/MCPGetToolCommand.class.st @@ -14,20 +14,11 @@ Class { { #category : 'instance creation' } MCPGetToolCommand class >> tool: aTool request: aRequest [ - ^ self - tool: aTool - request: aRequest - toolExposurePolicy: MCPToolExposurePolicy default + ^ self tool: aTool request: aRequest toolRegistry: MCPToolRegistry new ] { #category : 'executing' } MCPGetToolCommand >> execute [ - | catalogTool | - catalogTool := MCPToolRegistry - toolNamed: request toolName - ifAbsent: [ Error signal: 'Unknown MCP tool: ' , request toolName ] - usingPolicy: self toolExposurePolicy. - ^ { (#tool -> (MCPToolRegistry contractForTool: catalogTool usingPolicy: self toolExposurePolicy includeOutputSchema: false)) } - asDictionary + ^ { (#tool -> (self toolRegistry contractForToolNamed: request toolName includeOutputSchema: false)) } asDictionary ] diff --git a/src/MCP/MCPSearchToolsCommand.class.st b/src/MCP/MCPSearchToolsCommand.class.st index b0239fc..047e9fd 100644 --- a/src/MCP/MCPSearchToolsCommand.class.st +++ b/src/MCP/MCPSearchToolsCommand.class.st @@ -14,10 +14,7 @@ Class { { #category : 'instance creation' } MCPSearchToolsCommand class >> tool: aTool request: aRequest [ - ^ self - tool: aTool - request: aRequest - toolExposurePolicy: MCPToolExposurePolicy default + ^ self tool: aTool request: aRequest toolRegistry: MCPToolRegistry new ] { #category : 'executing' } @@ -25,20 +22,11 @@ MCPSearchToolsCommand >> execute [ | data matchingTools pageSize visibleTools | pageSize := self pageSize. - matchingTools := MCPToolRegistry - toolsMatchingQuery: request query - group: request group - usingPolicy: self toolExposurePolicy. + matchingTools := self toolRegistry toolsMatchingQuery: request query group: request group. visibleTools := self visibleToolsFrom: matchingTools. data := Dictionary new. - data - at: #tools - put: ((visibleTools collect: [ :each | - MCPToolRegistry - searchMetadataForTool: each - usingPolicy: self toolExposurePolicy ]) asArray). - (self hasMoreToolsAfter: matchingTools) ifTrue: [ - data at: #nextCursor put: (request cursor + pageSize) asString ]. + data at: #tools put: (visibleTools collect: [ :each | each searchMetadata ]) asArray. + (self hasMoreToolsAfter: matchingTools) ifTrue: [ data at: #nextCursor put: (request cursor + pageSize) asString ]. ^ data ] diff --git a/src/MCP/MCPStructureToolAnnotations.class.st b/src/MCP/MCPStructureToolAnnotations.class.st new file mode 100644 index 0000000..9b9166c --- /dev/null +++ b/src/MCP/MCPStructureToolAnnotations.class.st @@ -0,0 +1,106 @@ +" +MCP protocol structure for optional tool behavior hints. Tool annotations are advisory metadata; clients must not treat them as trusted authorization decisions. +" +Class { + #name : 'MCPStructureToolAnnotations', + #superclass : 'Object', + #instVars : [ + 'readOnlyHint', + 'destructiveHint', + 'idempotentHint', + 'openWorldHint', + 'title' + ], + #category : 'MCP-Structure', + #package : 'MCP', + #tag : 'Structure' +} + +{ #category : 'instance creation' } +MCPStructureToolAnnotations class >> mayModify [ + + ^ self new + readOnlyHint: false; + yourself +] + +{ #category : 'instance creation' } +MCPStructureToolAnnotations class >> readOnly [ + + ^ self new + readOnlyHint: true; + yourself +] + +{ #category : 'converting' } +MCPStructureToolAnnotations >> asJRPCJSON [ + + | dictionary | + dictionary := Dictionary new. + self title ifNotNil: [ :annotationTitle | dictionary at: #title put: annotationTitle ]. + self readOnlyHint ifNotNil: [ :hint | dictionary at: #readOnlyHint put: hint ]. + self destructiveHint ifNotNil: [ :hint | dictionary at: #destructiveHint put: hint ]. + self idempotentHint ifNotNil: [ :hint | dictionary at: #idempotentHint put: hint ]. + self openWorldHint ifNotNil: [ :hint | dictionary at: #openWorldHint put: hint ]. + ^ dictionary +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> destructiveHint [ + + ^ destructiveHint +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> destructiveHint: aBoolean [ + + destructiveHint := aBoolean +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> idempotentHint [ + + ^ idempotentHint +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> idempotentHint: aBoolean [ + + idempotentHint := aBoolean +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> openWorldHint [ + + ^ openWorldHint +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> openWorldHint: aBoolean [ + + openWorldHint := aBoolean +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> readOnlyHint [ + + ^ readOnlyHint +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> readOnlyHint: aBoolean [ + + readOnlyHint := aBoolean +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> title [ + + ^ title +] + +{ #category : 'accessing' } +MCPStructureToolAnnotations >> title: aString [ + + title := aString +] diff --git a/src/MCP/MCPTool.class.st b/src/MCP/MCPTool.class.st index c082a82..1177503 100644 --- a/src/MCP/MCPTool.class.st +++ b/src/MCP/MCPTool.class.st @@ -1,7 +1,7 @@ " Abstract base for MCP tools exposed by the server. -A tool owns its public MCP metadata: name, title, description, icons, input schema, and output schema. The server uses that metadata when advertising tools and when validating an incoming tool call. +A tool owns its public MCP metadata: name, title, description, icons, annotations, input schema, and output schema. The server uses that metadata when advertising tools and when validating an incoming tool call. The normal execution flow is: 1. The MCP server receives a JSON-RPC tool call and dispatches it to the matching MCPTool instance. @@ -26,22 +26,116 @@ Class { #tag : 'Tools' } +{ #category : 'metadata - groups' } +MCPTool class >> builtInGroupNames [ + + ^ { + self classesGroupName. + self debuggingGroupName. + self historyGroupName. + self methodsGroupName. + self packagesGroupName. + self repositoriesGroupName. + self runtimeGroupName. + self testsGroupName. + self toolCatalogGroupName. + self uiGroupName } +] + +{ #category : 'metadata - groups' } +MCPTool class >> classesGroupName [ + + ^ 'classes' +] + { #category : 'querying' } MCPTool class >> concreteSubclasses [ ^ self allSubclasses reject: [ :each | each isAbstract ] ] +{ #category : 'metadata - groups' } +MCPTool class >> debuggingGroupName [ + + ^ 'debugging' +] + +{ #category : 'metadata' } +MCPTool class >> groupName [ + + ^ self otherGroupName +] + +{ #category : 'metadata - groups' } +MCPTool class >> historyGroupName [ + + ^ 'history' +] + { #category : 'testing' } MCPTool class >> isAbstract [ ^ self = MCPTool ] +{ #category : 'metadata' } +MCPTool class >> isBuiltIn [ + + ^ self package = MCPTool package +] + +{ #category : 'metadata - groups' } +MCPTool class >> methodsGroupName [ + + ^ 'methods' +] + +{ #category : 'instance creation' } +MCPTool class >> newForRegistry: aToolRegistry [ + + ^ self new +] + +{ #category : 'metadata - groups' } +MCPTool class >> otherGroupName [ + + ^ 'other' +] + +{ #category : 'metadata - groups' } +MCPTool class >> packagesGroupName [ + + ^ 'packages' +] + +{ #category : 'metadata - groups' } +MCPTool class >> repositoriesGroupName [ + + ^ 'repositories' +] + +{ #category : 'metadata - groups' } +MCPTool class >> runtimeGroupName [ + + ^ 'runtime' +] + +{ #category : 'metadata - groups' } +MCPTool class >> testsGroupName [ + + ^ 'tests' +] + +{ #category : 'metadata - groups' } +MCPTool class >> toolCatalogGroupName [ + + ^ 'tool-catalog' +] + { #category : 'scripts' } MCPTool class >> toolGroups [ - ^ MCPToolRegistry publicToolGroups + ^ MCPToolRegistry new publicToolGroups ] { #category : 'scripts' } @@ -69,18 +163,35 @@ MCPTool class >> toolName [ self subclassResponsibility ] +{ #category : 'metadata - groups' } +MCPTool class >> uiGroupName [ + + ^ 'ui' +] + +{ #category : 'metadata' } +MCPTool >> additionalKeywords [ + + ^ #( ) +] + +{ #category : 'metadata' } +MCPTool >> annotations [ + + ^ MCPStructureToolAnnotations mayModify +] + { #category : 'accessing' } MCPTool >> asJRPCJSON [ - | dic | - dic := Dictionary new. - self description ifNotNil: [ - dic at: #description put: self description asJRPCJSON ]. - self inputSchema ifNotNil: [ - dic at: #inputSchema put: self inputSchema asJRPCJSON ]. - self name ifNotNil: [ dic at: #name put: self name asJRPCJSON ]. - self title ifNotNil: [ dic at: #title put: self title asJRPCJSON ]. - ^ dic + | dictionary | + dictionary := Dictionary new. + self annotations ifNotNil: [ :toolAnnotations | dictionary at: #annotations put: toolAnnotations asJRPCJSON ]. + self description ifNotNil: [ dictionary at: #description put: self description asJRPCJSON ]. + self inputSchema ifNotNil: [ dictionary at: #inputSchema put: self inputSchema asJRPCJSON ]. + self name ifNotNil: [ dictionary at: #name put: self name asJRPCJSON ]. + self title ifNotNil: [ dictionary at: #title put: self title asJRPCJSON ]. + ^ dictionary ] { #category : 'private - schema' } @@ -120,6 +231,22 @@ MCPTool >> commandForRequest: aToolRequest [ self subclassResponsibility ] +{ #category : 'metadata' } +MCPTool >> contractIncludingOutputSchema: includeOutputSchema [ + + | data | + data := self metadata copy. + data at: #inputSchema put: self inputSchema asJRPCJSON. + includeOutputSchema ifTrue: [ data at: #outputSchema put: self outputSchema asJRPCJSON ]. + ^ data +] + +{ #category : 'metadata' } +MCPTool >> defaultExposure [ + + ^ 'discoverable' +] + { #category : 'metadata' } MCPTool >> description [ @@ -243,12 +370,6 @@ MCPTool >> executeWithRequest: aRequest [ self subclassResponsibility ] -{ #category : 'executing' } -MCPTool >> executeWithRequest: aRequest usingPolicy: aToolExposurePolicy [ - - ^ self executeWithRequest: aRequest -] - { #category : 'error handling' } MCPTool >> failureMessageFor: anError [ @@ -269,6 +390,12 @@ MCPTool >> failureMessageFor: anError [ nextPutAll: message ] ] ] +{ #category : 'metadata' } +MCPTool >> groupName [ + + ^ self class groupName +] + { #category : 'metadata' } MCPTool >> icons [ @@ -300,6 +427,31 @@ MCPTool >> integerSchemaPropertyNamed: propertyName description: propertyDescrip yourself ] +{ #category : 'metadata' } +MCPTool >> keywords [ + + | keywords normalizedKeywords | + keywords := OrderedCollection new. + keywords addAll: (self name findTokens: '-_'). + keywords addAll: (self title findTokens: ' -'). + keywords addAll: (self groupName findTokens: '-_'). + keywords addAll: self additionalKeywords. + normalizedKeywords := OrderedCollection new. + keywords do: [ :each | normalizedKeywords addAll: (each asString findTokens: '-_ ') ]. + ^ (normalizedKeywords collect: [ :each | each asLowercase ]) asSet asArray +] + +{ #category : 'metadata' } +MCPTool >> metadata [ + + ^ { + (#name -> self name). + (#description -> self description). + (#group -> self groupName). + (#annotations -> self annotations asJRPCJSON). + (#keywords -> self keywords) } asDictionary +] + { #category : 'metadata' } MCPTool >> name [ @@ -360,6 +512,32 @@ MCPTool >> schemaPropertyNamed: aName type: aType description: aDescription [ yourself ] +{ #category : 'metadata' } +MCPTool >> searchMetadata [ + + ^ { + (#name -> self name). + (#description -> self description) } asDictionary +] + +{ #category : 'metadata' } +MCPTool >> searchableText [ + + ^ String streamContents: [ :stream | + stream + nextPutAll: self name; + space; + nextPutAll: self title; + space; + nextPutAll: self description; + space; + nextPutAll: self groupName. + self keywords do: [ :keyword | + stream + space; + nextPutAll: keyword ] ] +] + { #category : 'private - schema' } MCPTool >> shouldAdvertiseStringArrayItemDescription: anItemDescription [ diff --git a/src/MCP/MCPToolCallTool.class.st b/src/MCP/MCPToolCallTool.class.st index fac3861..9a10ae4 100644 --- a/src/MCP/MCPToolCallTool.class.st +++ b/src/MCP/MCPToolCallTool.class.st @@ -11,6 +11,12 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolCallTool class >> groupName [ + + ^ self toolCatalogGroupName +] + { #category : 'metadata' } MCPToolCallTool class >> toolName [ @@ -68,6 +74,12 @@ MCPToolCallTool >> commandForRequest: callRequest [ ^ MCPCallToolCommand tool: self request: callRequest ] +{ #category : 'metadata' } +MCPToolCallTool >> defaultExposure [ + + ^ 'static' +] + { #category : 'metadata' } MCPToolCallTool >> description [ diff --git a/src/MCP/MCPToolCaptureScreenshot.class.st b/src/MCP/MCPToolCaptureScreenshot.class.st index 2ac3607..fc6411f 100644 --- a/src/MCP/MCPToolCaptureScreenshot.class.st +++ b/src/MCP/MCPToolCaptureScreenshot.class.st @@ -11,12 +11,30 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolCaptureScreenshot class >> groupName [ + + ^ self uiGroupName +] + { #category : 'metadata' } MCPToolCaptureScreenshot class >> toolName [ ^ 'screenshot_capture' ] +{ #category : 'metadata' } +MCPToolCaptureScreenshot >> additionalKeywords [ + + ^ #( 'screenshot' 'capture' 'window' 'display' 'world' 'ui' ) +] + +{ #category : 'metadata' } +MCPToolCaptureScreenshot >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'metadata' } MCPToolCaptureScreenshot >> buildInputSchema [ diff --git a/src/MCP/MCPToolCatalogCommand.class.st b/src/MCP/MCPToolCatalogCommand.class.st index dc94986..696a603 100644 --- a/src/MCP/MCPToolCatalogCommand.class.st +++ b/src/MCP/MCPToolCatalogCommand.class.st @@ -1,11 +1,11 @@ " -Abstract command for tool-catalog operations that need the current exposure policy. +Abstract command for tool-catalog operations. Catalog commands receive the registry whose tools and effective exposure metadata they query. " Class { #name : 'MCPToolCatalogCommand', #superclass : 'MCPToolRequestCommand', #instVars : [ - 'toolExposurePolicy' + 'toolRegistry' ], #category : 'MCP-Commands', #package : 'MCP', @@ -19,23 +19,22 @@ MCPToolCatalogCommand class >> isAbstract [ ] { #category : 'instance creation' } -MCPToolCatalogCommand class >> tool: aTool request: aRequest toolExposurePolicy: aToolExposurePolicy [ +MCPToolCatalogCommand class >> tool: aTool request: aRequest toolRegistry: aToolRegistry [ ^ self new initializeTool: aTool request: aRequest; - toolExposurePolicy: aToolExposurePolicy; + toolRegistry: aToolRegistry; yourself ] { #category : 'accessing' } -MCPToolCatalogCommand >> toolExposurePolicy [ +MCPToolCatalogCommand >> toolRegistry [ - ^ toolExposurePolicy ifNil: [ MCPToolExposurePolicy default ] + ^ toolRegistry ] { #category : 'accessing' } -MCPToolCatalogCommand >> toolExposurePolicy: aToolExposurePolicy [ +MCPToolCatalogCommand >> toolRegistry: aToolRegistry [ - toolExposurePolicy := aToolExposurePolicy ifNil: [ - MCPToolExposurePolicy default ] + toolRegistry := aToolRegistry ] diff --git a/src/MCP/MCPToolChangeHistory.class.st b/src/MCP/MCPToolChangeHistory.class.st index f157e3b..ef673f1 100644 --- a/src/MCP/MCPToolChangeHistory.class.st +++ b/src/MCP/MCPToolChangeHistory.class.st @@ -11,12 +11,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolChangeHistory class >> groupName [ + + ^ self historyGroupName +] + { #category : 'testing' } MCPToolChangeHistory class >> isAbstract [ ^ self = MCPToolChangeHistory ] +{ #category : 'metadata' } +MCPToolChangeHistory >> additionalKeywords [ + + ^ #( 'history' 'change' 'epicea' 'ombu' 'recover' ) +] + { #category : 'private - schema' } MCPToolChangeHistory >> changeHistoryEntryArraySchemaNamed: propertyName description: propertyDescription [ diff --git a/src/MCP/MCPToolCheckoutRepositoryBranch.class.st b/src/MCP/MCPToolCheckoutRepositoryBranch.class.st index b54b937..a468986 100644 --- a/src/MCP/MCPToolCheckoutRepositoryBranch.class.st +++ b/src/MCP/MCPToolCheckoutRepositoryBranch.class.st @@ -15,6 +15,12 @@ MCPToolCheckoutRepositoryBranch class >> toolName [ ^ 'repository_branch_checkout' ] +{ #category : 'metadata' } +MCPToolCheckoutRepositoryBranch >> additionalKeywords [ + + ^ super additionalKeywords , #( 'checkout' 'reload' 'stale' 'verification' ) +] + { #category : 'private - specs' } MCPToolCheckoutRepositoryBranch >> repositoryToolSpec [ diff --git a/src/MCP/MCPToolClassMutation.class.st b/src/MCP/MCPToolClassMutation.class.st index cd977e1..a28e9f9 100644 --- a/src/MCP/MCPToolClassMutation.class.st +++ b/src/MCP/MCPToolClassMutation.class.st @@ -13,12 +13,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolClassMutation class >> groupName [ + + ^ self classesGroupName +] + { #category : 'testing' } MCPToolClassMutation class >> isAbstract [ ^ self = MCPToolClassMutation ] +{ #category : 'metadata' } +MCPToolClassMutation >> additionalKeywords [ + + ^ #( 'class' 'refactor' 'refactoring' ) +] + { #category : 'private - schema' } MCPToolClassMutation >> atLeastOneInputProperties [ diff --git a/src/MCP/MCPToolCompileMethod.class.st b/src/MCP/MCPToolCompileMethod.class.st index c204f73..12aa0f1 100644 --- a/src/MCP/MCPToolCompileMethod.class.st +++ b/src/MCP/MCPToolCompileMethod.class.st @@ -17,6 +17,12 @@ MCPToolCompileMethod class >> toolName [ ^ 'method_compile' ] +{ #category : 'metadata' } +MCPToolCompileMethod >> defaultExposure [ + + ^ 'static' +] + { #category : 'private - specs' } MCPToolCompileMethod >> methodToolSpec [ diff --git a/src/MCP/MCPToolCreateClass.class.st b/src/MCP/MCPToolCreateClass.class.st index e1bc1d8..e2e5f5f 100644 --- a/src/MCP/MCPToolCreateClass.class.st +++ b/src/MCP/MCPToolCreateClass.class.st @@ -40,3 +40,9 @@ MCPToolCreateClass >> classToolSpec [ -> #( 'className' 'superclassName' 'packageName' )) } asDictionary ] + +{ #category : 'metadata' } +MCPToolCreateClass >> defaultExposure [ + + ^ 'static' +] diff --git a/src/MCP/MCPToolDebug.class.st b/src/MCP/MCPToolDebug.class.st index a3d1b9d..81c5e1a 100644 --- a/src/MCP/MCPToolDebug.class.st +++ b/src/MCP/MCPToolDebug.class.st @@ -11,12 +11,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolDebug class >> groupName [ + + ^ self debuggingGroupName +] + { #category : 'testing' } MCPToolDebug class >> isAbstract [ ^ self = MCPToolDebug ] +{ #category : 'metadata' } +MCPToolDebug >> additionalKeywords [ + + ^ #( 'debug' 'session' 'state' 'frame' 'breakpoint' 'exception' 'repair' 'control' ) +] + { #category : 'private - schema' } MCPToolDebug >> boundedIntegerSchemaPropertyNamed: propertyName description: propertyDescription default: defaultValue maximum: maximumValue [ diff --git a/src/MCP/MCPToolDebugState.class.st b/src/MCP/MCPToolDebugState.class.st index 05e4998..8284753 100644 --- a/src/MCP/MCPToolDebugState.class.st +++ b/src/MCP/MCPToolDebugState.class.st @@ -17,6 +17,12 @@ MCPToolDebugState class >> toolName [ ^ 'debug_state_get' ] +{ #category : 'metadata' } +MCPToolDebugState >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'metadata' } MCPToolDebugState >> buildInputSchema [ diff --git a/src/MCP/MCPToolDebugVariables.class.st b/src/MCP/MCPToolDebugVariables.class.st index d11a646..cda9355 100644 --- a/src/MCP/MCPToolDebugVariables.class.st +++ b/src/MCP/MCPToolDebugVariables.class.st @@ -17,6 +17,12 @@ MCPToolDebugVariables class >> toolName [ ^ 'debug_variable_get' ] +{ #category : 'metadata' } +MCPToolDebugVariables >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'metadata' } MCPToolDebugVariables >> buildInputSchema [ diff --git a/src/MCP/MCPToolEvaluate.class.st b/src/MCP/MCPToolEvaluate.class.st index 4cd6c1d..0fa256a 100644 --- a/src/MCP/MCPToolEvaluate.class.st +++ b/src/MCP/MCPToolEvaluate.class.st @@ -9,12 +9,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolEvaluate class >> groupName [ + + ^ self runtimeGroupName +] + { #category : 'metadata' } MCPToolEvaluate class >> toolName [ ^ 'image_evaluate' ] +{ #category : 'metadata' } +MCPToolEvaluate >> additionalKeywords [ + + ^ #( 'expression' 'smalltalk' ) +] + { #category : 'metadata' } MCPToolEvaluate >> buildInputSchema [ @@ -58,6 +70,12 @@ MCPToolEvaluate >> commandForRequest: evaluateRequest [ resultPreviewCharacterLimit: self resultPreviewCharacterLimit ] +{ #category : 'metadata' } +MCPToolEvaluate >> defaultExposure [ + + ^ 'static' +] + { #category : 'metadata' } MCPToolEvaluate >> description [ diff --git a/src/MCP/MCPToolExposurePolicy.class.st b/src/MCP/MCPToolExposurePolicy.class.st index a13df88..627ef5e 100644 --- a/src/MCP/MCPToolExposurePolicy.class.st +++ b/src/MCP/MCPToolExposurePolicy.class.st @@ -1,13 +1,12 @@ " -I decide which registered MCP tools are advertised directly through tools/list for one MCP server instance. - -The default policy follows each tool registration's default exposure. Supplying staticToolNames: switches to an explicit static surface; all other registered tools remain discoverable through tool_call. +Pure visibility policy for MCP tool registrations. It applies tool defaults unless configured with an explicit static tool-name set or to expose every registration statically. Registry lookup and configuration validation remain the responsibility of MCPToolRegistry. " Class { #name : 'MCPToolExposurePolicy', #superclass : 'Object', #instVars : [ - 'staticToolNames' + 'staticToolNames', + 'exposeAllTools' ], #category : 'MCP-Tools', #package : 'MCP', @@ -17,7 +16,9 @@ Class { { #category : 'instance creation' } MCPToolExposurePolicy class >> allStatic [ - ^ self staticToolNames: MCPToolRegistry publicToolNames + ^ self new + exposeAllTools; + yourself ] { #category : 'instance creation' } @@ -34,15 +35,26 @@ MCPToolExposurePolicy class >> staticToolNames: aCollection [ yourself ] -{ #category : 'tools' } -MCPToolExposurePolicy >> exposureForTool: aTool [ +{ #category : 'accessing' } +MCPToolExposurePolicy >> explicitStaticToolNames [ - ^ self exposureForToolRegistration: (MCPToolRegistry - registrationForTool: aTool - ifAbsent: [ ^ 'discoverable' ]) + ^ staticToolNames ] -{ #category : 'tools' } +{ #category : 'configuration' } +MCPToolExposurePolicy >> exposeAllTools [ + + exposeAllTools := true. + staticToolNames := nil +] + +{ #category : 'testing' } +MCPToolExposurePolicy >> exposesAllTools [ + + ^ exposeAllTools +] + +{ #category : 'accessing' } MCPToolExposurePolicy >> exposureForToolRegistration: aToolRegistration [ ^ (self isStaticToolRegistration: aToolRegistration) @@ -56,39 +68,24 @@ MCPToolExposurePolicy >> hasExplicitStaticToolNames [ ^ staticToolNames notNil ] -{ #category : 'testing' } -MCPToolExposurePolicy >> isStaticTool: aToolName [ +{ #category : 'initialization' } +MCPToolExposurePolicy >> initialize [ - ^ self isStaticToolRegistration: - (MCPToolRegistry - registrationNamed: aToolName - ifAbsent: [ ^ false ]) + super initialize. + exposeAllTools := false ] { #category : 'testing' } MCPToolExposurePolicy >> isStaticToolRegistration: aToolRegistration [ - self hasExplicitStaticToolNames ifTrue: [ - ^ staticToolNames includes: aToolRegistration name ]. + self exposesAllTools ifTrue: [ ^ true ]. + self hasExplicitStaticToolNames ifTrue: [ ^ staticToolNames includes: aToolRegistration name ]. ^ aToolRegistration isStaticByDefault ] -{ #category : 'accessing' } -MCPToolExposurePolicy >> staticToolNames [ - - ^ staticToolNames ifNil: [ MCPToolRegistry defaultStaticToolNames ] -] - { #category : 'accessing' } MCPToolExposurePolicy >> staticToolNames: aCollection [ - | normalized unknown | - normalized := (aCollection collect: [ :each | each asString ]) - asArray. - unknown := normalized reject: [ :each | - MCPToolRegistry includesToolNamed: each ]. - unknown ifNotEmpty: [ - Error signal: - 'Unknown MCP tool(s) in exposure policy: ' , unknown printString ]. - staticToolNames := normalized + staticToolNames := (aCollection collect: [ :each | each asString ]) asArray. + exposeAllTools := false ] diff --git a/src/MCP/MCPToolGetClass.class.st b/src/MCP/MCPToolGetClass.class.st index 4c9e98a..a1dc38d 100644 --- a/src/MCP/MCPToolGetClass.class.st +++ b/src/MCP/MCPToolGetClass.class.st @@ -11,12 +11,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolGetClass class >> groupName [ + + ^ self classesGroupName +] + { #category : 'metadata' } MCPToolGetClass class >> toolName [ ^ 'class_get' ] +{ #category : 'metadata' } +MCPToolGetClass >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'metadata' } MCPToolGetClass >> buildInputSchema [ @@ -200,6 +212,12 @@ MCPToolGetClass >> commandForRequest: getRequest [ ^ MCPGetClassCommand tool: self request: getRequest ] +{ #category : 'metadata' } +MCPToolGetClass >> defaultExposure [ + + ^ 'static' +] + { #category : 'metadata' } MCPToolGetClass >> description [ diff --git a/src/MCP/MCPToolGetDebugSession.class.st b/src/MCP/MCPToolGetDebugSession.class.st index c611c1d..e6185f9 100644 --- a/src/MCP/MCPToolGetDebugSession.class.st +++ b/src/MCP/MCPToolGetDebugSession.class.st @@ -15,6 +15,12 @@ MCPToolGetDebugSession class >> toolName [ ^ 'debug_session_get' ] +{ #category : 'metadata' } +MCPToolGetDebugSession >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'private - specs' } MCPToolGetDebugSession >> sessionToolSpec [ diff --git a/src/MCP/MCPToolGetMethod.class.st b/src/MCP/MCPToolGetMethod.class.st index 215dc33..05e0036 100644 --- a/src/MCP/MCPToolGetMethod.class.st +++ b/src/MCP/MCPToolGetMethod.class.st @@ -11,12 +11,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolGetMethod class >> groupName [ + + ^ self methodsGroupName +] + { #category : 'metadata' } MCPToolGetMethod class >> toolName [ ^ 'method_get' ] +{ #category : 'metadata' } +MCPToolGetMethod >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'private - analysis' } MCPToolGetMethod >> bindingClassNameFor: aBinding [ @@ -105,6 +117,12 @@ MCPToolGetMethod >> commandForRequest: getRequest [ ^ MCPGetMethodCommand tool: self request: getRequest ] +{ #category : 'metadata' } +MCPToolGetMethod >> defaultExposure [ + + ^ 'static' +] + { #category : 'private - analysis' } MCPToolGetMethod >> definingClassNameFromBinding: aBinding [ diff --git a/src/MCP/MCPToolGetTool.class.st b/src/MCP/MCPToolGetTool.class.st index 972c87d..0310ad7 100644 --- a/src/MCP/MCPToolGetTool.class.st +++ b/src/MCP/MCPToolGetTool.class.st @@ -1,22 +1,68 @@ " -Static bridge tool for reading a catalog tool's full contract. +Static bridge tool for reading a tool contract from its owning registry. Agents use it after tool_search and before tool_call when a discoverable tool requires exact schema details. " Class { #name : 'MCPToolGetTool', #superclass : 'MCPTool', + #instVars : [ + 'toolRegistry' + ], #category : 'MCP-Tools', #package : 'MCP', #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolGetTool class >> groupName [ + + ^ self toolCatalogGroupName +] + +{ #category : 'instance creation' } +MCPToolGetTool class >> newForRegistry: aToolRegistry [ + + ^ self new + toolRegistry: aToolRegistry; + yourself +] + { #category : 'metadata' } MCPToolGetTool class >> toolName [ ^ 'tool_get' ] +{ #category : 'metadata' } +MCPToolGetTool >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + +{ #category : 'private - schema' } +MCPToolGetTool >> annotationsSchemaProperty [ + + | property | + property := self schemaPropertyNamed: 'annotations' type: 'object' description: 'Standard MCP tool behavior hints.'. + property + properties: { + (self schemaPropertyNamed: 'title' type: 'string' description: 'Optional annotation title.'). + (self schemaPropertyNamed: 'readOnlyHint' type: 'boolean' description: 'Whether the tool leaves its environment unchanged.'). + (self + schemaPropertyNamed: 'destructiveHint' + type: 'boolean' + description: 'Whether a modifying tool may perform destructive updates.'). + (self schemaPropertyNamed: 'idempotentHint' type: 'boolean' description: 'Whether repeated calls have no additional effect.'). + (self + schemaPropertyNamed: 'openWorldHint' + type: 'boolean' + description: 'Whether the tool may interact with external entities.') }; + required: #( 'readOnlyHint' ); + additionalProperties: false. + ^ property +] + { #category : 'metadata' } MCPToolGetTool >> buildInputSchema [ @@ -34,98 +80,69 @@ MCPToolGetTool >> buildInputSchema [ { #category : 'metadata' } MCPToolGetTool >> buildOutputSchema [ - | activationRequirementsProperty examplesProperty inputSchemaProperty outputSchemaProperty schemaProperty toolProperty | - schemaProperty := MCPStructureProperties new - type: 'object'; - description: 'JSON Schema object.'; - additionalProperties: true; - yourself. - inputSchemaProperty := schemaProperty copy - name: 'inputSchema'; - yourself. - outputSchemaProperty := schemaProperty copy - name: 'outputSchema'; - yourself. - examplesProperty := (self - schemaPropertyNamed: 'examples' - type: 'array' - description: 'Example calls.') - items: (MCPStructureProperties new - type: 'object'; - additionalProperties: true; - yourself); - yourself. - activationRequirementsProperty := (self - schemaPropertyNamed: 'activationRequirements' - type: 'array' - description: 'Requirements before calling tool.') - items: (MCPStructureProperties new - type: 'string'; - yourself); - yourself. - toolProperty := self - schemaPropertyNamed: 'tool' - type: 'object' - description: 'Tool contract.'. + | inputSchemaProperty toolProperty | + inputSchemaProperty := MCPStructureProperties new + name: 'inputSchema'; + type: 'object'; + description: 'JSON Schema object.'; + additionalProperties: true; + yourself. + toolProperty := self schemaPropertyNamed: 'tool' type: 'object' description: 'Tool contract.'. toolProperty properties: { - (self - schemaPropertyNamed: 'name' - type: 'string' - description: 'Tool name.'). - (self - schemaPropertyNamed: 'description' - type: 'string' - description: 'Tool description.'). - inputSchemaProperty. - outputSchemaProperty. - examplesProperty. - activationRequirementsProperty }; - required: #( 'name' 'description' 'inputSchema' ); + (self schemaPropertyNamed: 'name' type: 'string' description: 'Tool name.'). + (self schemaPropertyNamed: 'description' type: 'string' description: 'Tool description.'). + (self schemaPropertyNamed: 'group' type: 'string' description: 'Tool catalog group.'). + (self schemaPropertyNamed: 'exposure' type: 'string' description: 'Effective tool exposure.'). + self annotationsSchemaProperty. + self keywordsSchemaProperty. + inputSchemaProperty }; + required: #( 'name' 'description' 'group' 'exposure' 'annotations' 'keywords' 'inputSchema' ); additionalProperties: false. - ^ self - standardOutputSchemaForDataProperties: { toolProperty } - required: #( 'tool' ) + ^ self standardOutputSchemaForDataProperties: { toolProperty } required: #( 'tool' ) ] { #category : 'private - execution' } MCPToolGetTool >> commandForRequest: getRequest [ - ^ MCPGetToolCommand tool: self request: getRequest + ^ MCPGetToolCommand tool: self request: getRequest toolRegistry: self toolRegistry ] { #category : 'metadata' } -MCPToolGetTool >> description [ +MCPToolGetTool >> defaultExposure [ - ^ 'Return one tool contract with metadata and input schema.' + ^ 'static' ] -{ #category : 'executing' } -MCPToolGetTool >> executeWithRequest: request [ +{ #category : 'metadata' } +MCPToolGetTool >> description [ - ^ self - executeWithRequest: request - usingPolicy: MCPToolExposurePolicy default + ^ 'Return one tool contract with metadata and input schema.' ] { #category : 'executing' } -MCPToolGetTool >> executeWithRequest: request usingPolicy: aToolExposurePolicy [ +MCPToolGetTool >> executeWithRequest: request [ ^ self executeParsedRequestFrom: request do: [ :getRequest | | contract data | - data := (MCPGetToolCommand - tool: self - request: getRequest - toolExposurePolicy: aToolExposurePolicy) execute. + data := (self commandForRequest: getRequest) execute. contract := data at: #tool. - self - successResultText: 'Tool ' , (contract at: #name) , ' found.' - data: data ] + self successResultText: 'Tool ' , (contract at: #name) , ' found.' data: data ] onError: [ :error :parsedRequest | self errorResultFor: error ] ] +{ #category : 'private - schema' } +MCPToolGetTool >> keywordsSchemaProperty [ + + ^ (self schemaPropertyNamed: 'keywords' type: 'array' description: 'Search terms associated with the tool.') + items: (MCPStructureProperties new + type: 'string'; + yourself); + yourself +] + { #category : 'private - request' } MCPToolGetTool >> parsedRequestFromToolRequest: request [ @@ -137,3 +154,15 @@ MCPToolGetTool >> title [ ^ 'Get Tool' ] + +{ #category : 'accessing' } +MCPToolGetTool >> toolRegistry [ + + ^ toolRegistry ifNil: [ toolRegistry := MCPToolRegistry new ] +] + +{ #category : 'accessing' } +MCPToolGetTool >> toolRegistry: aToolRegistry [ + + toolRegistry := aToolRegistry +] diff --git a/src/MCP/MCPToolListChangeHistoryEntries.class.st b/src/MCP/MCPToolListChangeHistoryEntries.class.st index 83d2008..56f9cd3 100644 --- a/src/MCP/MCPToolListChangeHistoryEntries.class.st +++ b/src/MCP/MCPToolListChangeHistoryEntries.class.st @@ -15,6 +15,12 @@ MCPToolListChangeHistoryEntries class >> toolName [ ^ 'history_entry_list' ] +{ #category : 'metadata' } +MCPToolListChangeHistoryEntries >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'metadata' } MCPToolListChangeHistoryEntries >> buildInputSchema [ diff --git a/src/MCP/MCPToolListChangeHistoryFiles.class.st b/src/MCP/MCPToolListChangeHistoryFiles.class.st index 9faee14..b4dc376 100644 --- a/src/MCP/MCPToolListChangeHistoryFiles.class.st +++ b/src/MCP/MCPToolListChangeHistoryFiles.class.st @@ -15,6 +15,12 @@ MCPToolListChangeHistoryFiles class >> toolName [ ^ 'history_file_list' ] +{ #category : 'metadata' } +MCPToolListChangeHistoryFiles >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'metadata' } MCPToolListChangeHistoryFiles >> buildInputSchema [ diff --git a/src/MCP/MCPToolListDebugBreakpoints.class.st b/src/MCP/MCPToolListDebugBreakpoints.class.st index 0955137..31f62ef 100644 --- a/src/MCP/MCPToolListDebugBreakpoints.class.st +++ b/src/MCP/MCPToolListDebugBreakpoints.class.st @@ -15,6 +15,12 @@ MCPToolListDebugBreakpoints class >> toolName [ ^ 'debug_breakpoint_list' ] +{ #category : 'metadata' } +MCPToolListDebugBreakpoints >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'private - specs' } MCPToolListDebugBreakpoints >> breakpointToolSpec [ diff --git a/src/MCP/MCPToolListDebugSessions.class.st b/src/MCP/MCPToolListDebugSessions.class.st index 534c71a..1b5979a 100644 --- a/src/MCP/MCPToolListDebugSessions.class.st +++ b/src/MCP/MCPToolListDebugSessions.class.st @@ -15,6 +15,12 @@ MCPToolListDebugSessions class >> toolName [ ^ 'debug_session_list' ] +{ #category : 'metadata' } +MCPToolListDebugSessions >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'private - specs' } MCPToolListDebugSessions >> sessionToolSpec [ diff --git a/src/MCP/MCPToolListRepositoryChanges.class.st b/src/MCP/MCPToolListRepositoryChanges.class.st index f816180..5df3b44 100644 --- a/src/MCP/MCPToolListRepositoryChanges.class.st +++ b/src/MCP/MCPToolListRepositoryChanges.class.st @@ -15,6 +15,18 @@ MCPToolListRepositoryChanges class >> toolName [ ^ 'repository_change_list' ] +{ #category : 'metadata' } +MCPToolListRepositoryChanges >> additionalKeywords [ + + ^ super additionalKeywords , #( 'diff' 'status' 'change' ) +] + +{ #category : 'metadata' } +MCPToolListRepositoryChanges >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'private - specs' } MCPToolListRepositoryChanges >> repositoryToolSpec [ diff --git a/src/MCP/MCPToolLoadMetacello.class.st b/src/MCP/MCPToolLoadMetacello.class.st index f2569f9..9b82090 100644 --- a/src/MCP/MCPToolLoadMetacello.class.st +++ b/src/MCP/MCPToolLoadMetacello.class.st @@ -9,12 +9,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolLoadMetacello class >> groupName [ + + ^ self repositoriesGroupName +] + { #category : 'testing' } MCPToolLoadMetacello class >> isAbstract [ ^ self = MCPToolLoadMetacello ] +{ #category : 'metadata' } +MCPToolLoadMetacello >> additionalKeywords [ + + ^ #( 'baseline' 'metacello' 'load' 'package' ) +] + { #category : 'private - execution' } MCPToolLoadMetacello >> commandForRequest: loadRequest [ diff --git a/src/MCP/MCPToolMethodLookupOperation.class.st b/src/MCP/MCPToolMethodLookupOperation.class.st index 1af64ae..6f4cd6d 100644 --- a/src/MCP/MCPToolMethodLookupOperation.class.st +++ b/src/MCP/MCPToolMethodLookupOperation.class.st @@ -9,6 +9,12 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolMethodLookupOperation class >> groupName [ + + ^ self methodsGroupName +] + { #category : 'testing' } MCPToolMethodLookupOperation class >> isAbstract [ diff --git a/src/MCP/MCPToolMethodMutation.class.st b/src/MCP/MCPToolMethodMutation.class.st index 3cc6bee..a7c1729 100644 --- a/src/MCP/MCPToolMethodMutation.class.st +++ b/src/MCP/MCPToolMethodMutation.class.st @@ -13,6 +13,12 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolMethodMutation class >> groupName [ + + ^ self methodsGroupName +] + { #category : 'testing' } MCPToolMethodMutation class >> isAbstract [ @@ -31,6 +37,12 @@ MCPToolMethodMutation >> addParameterRefactoringForSelector: oldSelectorSymbol i newArgs: newArguments ] +{ #category : 'metadata' } +MCPToolMethodMutation >> additionalKeywords [ + + ^ #( 'method' 'refactor' 'refactoring' ) +] + { #category : 'private' } MCPToolMethodMutation >> allowedNonErrorCritiqueRuleClasses [ diff --git a/src/MCP/MCPToolMethodSearch.class.st b/src/MCP/MCPToolMethodSearch.class.st index 0451b37..bef84da 100644 --- a/src/MCP/MCPToolMethodSearch.class.st +++ b/src/MCP/MCPToolMethodSearch.class.st @@ -12,12 +12,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolMethodSearch class >> groupName [ + + ^ self methodsGroupName +] + { #category : 'testing' } MCPToolMethodSearch class >> isAbstract [ ^ self = MCPToolMethodSearch ] +{ #category : 'metadata' } +MCPToolMethodSearch >> additionalKeywords [ + + ^ #( 'method' ) +] + { #category : 'metadata' } MCPToolMethodSearch >> buildOutputSchema [ diff --git a/src/MCP/MCPToolRegistration.class.st b/src/MCP/MCPToolRegistration.class.st index 1d03b02..e87090d 100644 --- a/src/MCP/MCPToolRegistration.class.st +++ b/src/MCP/MCPToolRegistration.class.st @@ -1,15 +1,11 @@ " -I describe one registered MCP tool. - -The registry owns registrations so a tool can be instantiated once and reused. The registration carries catalog metadata that belongs to registration time, such as the public group and default exposure. Server-specific exposure policy remains outside the tool and registration. +Registry entry wrapping one reusable MCPTool instance. Group and default exposure are delegated to the tool so registrations do not duplicate intrinsic metadata. " Class { #name : 'MCPToolRegistration', #superclass : 'Object', #instVars : [ - 'tool', - 'groupName', - 'defaultExposure' + 'tool' ], #category : 'MCP-Tools', #package : 'MCP', @@ -17,40 +13,27 @@ Class { } { #category : 'instance creation' } -MCPToolRegistration class >> tool: aTool groupName: aGroupName defaultExposure: anExposure [ - - ^ self new - initializeTool: aTool - groupName: aGroupName - defaultExposure: anExposure -] - -{ #category : 'constants' } -MCPToolRegistration class >> validExposureNames [ +MCPToolRegistration class >> tool: aTool [ - ^ #( 'static' 'discoverable' ) + ^ self new initializeTool: aTool ] { #category : 'accessing' } MCPToolRegistration >> defaultExposure [ - ^ defaultExposure + ^ self tool defaultExposure ] { #category : 'accessing' } MCPToolRegistration >> groupName [ - ^ groupName + ^ self tool groupName ] { #category : 'initialization' } -MCPToolRegistration >> initializeTool: aTool groupName: aGroupName defaultExposure: anExposure [ +MCPToolRegistration >> initializeTool: aTool [ - tool := aTool. - groupName := aGroupName asString. - defaultExposure := anExposure asString. - (self class validExposureNames includes: defaultExposure) ifFalse: [ - Error signal: 'Unknown MCP tool exposure: ' , defaultExposure ] + tool := aTool ] { #category : 'testing' } diff --git a/src/MCP/MCPToolRegistry.class.st b/src/MCP/MCPToolRegistry.class.st index 86a42ee..017fb19 100644 --- a/src/MCP/MCPToolRegistry.class.st +++ b/src/MCP/MCPToolRegistry.class.st @@ -1,13 +1,14 @@ " -I hold the public MCP tool registry. +Instance-scoped catalog of MCP tools. -Each registration owns one reusable tool instance, its public group, and its default exposure. MCP server instances use an exposure policy to decide which registered tools are advertised directly through tools/list; discoverable tools remain callable through tool_call. +A registry discovers built-in concrete MCPTool subclasses, owns one reusable instance of each registered tool, owns the exposure policy applied to those registrations, and provides lookup, grouping, static-surface selection, contract composition, and catalog search. Intrinsic metadata belongs to tools; visibility overrides belong to MCPToolExposurePolicy. " Class { #name : 'MCPToolRegistry', #superclass : 'Object', - #classInstVars : [ - 'registrations' + #instVars : [ + 'registrations', + 'exposurePolicy' ], #category : 'MCP-Tools', #package : 'MCP', @@ -15,472 +16,113 @@ Class { } { #category : 'tools' } -MCPToolRegistry class >> activationRequirementsForTool: aTool [ +MCPToolRegistry >> contractForTool: aTool includeOutputSchema: includeOutputSchema [ - ^ #( ) + | contract | + contract := aTool contractIncludingOutputSchema: includeOutputSchema. + contract at: #exposure put: (self exposureForTool: aTool). + ^ contract ] { #category : 'tools' } -MCPToolRegistry class >> additionalKeywordsForTool: aTool [ +MCPToolRegistry >> contractForToolNamed: aToolName includeOutputSchema: includeOutputSchema [ - | toolName | - toolName := aTool name. - toolName = 'tool_get' ifTrue: [ - ^ #( 'tool' 'catalog' 'contract' 'schema' 'metadata' ) ]. - toolName = 'repository_search' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'branch' 'commit' 'remote' 'head' ) ]. - toolName = 'repository_identity_verify' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'guard' 'identity' ) ]. - toolName = 'repository_change_list' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'diff' 'status' 'change' ) ]. - toolName = 'repository_create' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'create' 'package' ) ]. - toolName = 'repository_attach' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'attach' 'package' ) ]. - toolName = 'repository_update' ifTrue: [ - ^ #( 'repo' 'iceberg' 'update' 'package' 'metadata' ) ]. - toolName = 'repository_export' ifTrue: [ - ^ #( 'repo' 'iceberg' 'tonel' 'export' 'write' ) ]. - toolName = 'repository_commit' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'commit' ) ]. - toolName = 'repository_fetch' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'remote' 'fetch' ) ]. - toolName = 'repository_pull' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'remote' 'pull' ) ]. - toolName = 'repository_push' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'remote' 'push' ) ]. - toolName = 'repository_branch_create' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'branch' 'create' ) ]. - toolName = 'repository_branch_switch' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'branch' 'switch' ) ]. - toolName = 'repository_branch_checkout' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'branch' 'checkout' 'reload' 'stale' - 'verification' ) ]. - toolName = 'repository_head_adopt' ifTrue: [ - ^ #( 'repo' 'iceberg' 'git' 'head' 'adopt' ) ]. - toolName = 'baseline_load' ifTrue: [ - ^ #( 'baseline' 'metacello' 'load' 'package' ) ]. - toolName = 'repository_load' ifTrue: [ - ^ #( 'repo' 'baseline' 'metacello' 'load' 'package' ) ]. - toolName = 'package_search' ifTrue: [ - ^ #( 'package' 'project' 'tag' ) ]. - toolName = 'class_search' ifTrue: [ - ^ #( 'class' 'package' 'tag' 'slot' 'trait' 'hierarchy' ) ]. - toolName = 'class_get' ifTrue: [ - ^ #( 'class' 'inspect' 'superclass' 'subclass' 'slot' 'trait' - 'comment' ) ]. - toolName = 'class_create' ifTrue: [ - ^ #( 'class' 'create' 'package' 'superclass' 'slot' 'trait' ) ]. - toolName = 'class_name_update' ifTrue: [ - ^ #( 'class' 'name' 'rename' 'update' 'refactor' 'refactoring' ) ]. - toolName = 'class_superclass_update' ifTrue: [ - ^ #( 'class' 'superclass' 'parent' 'update' 'reparent' 'refactor' - 'refactoring' ) ]. - toolName = 'class_package_update' ifTrue: [ - ^ #( 'class' 'package' 'tag' 'category' 'move' 'update' 'refactor' - 'refactoring' ) ]. - toolName = 'class_comment_update' ifTrue: [ - ^ #( 'class' 'comment' 'update' ) ]. - toolName = 'class_slots_update' ifTrue: [ - ^ #( 'class' 'slot' 'definition' 'update' ) ]. - toolName = 'class_traits_update' ifTrue: [ - ^ #( 'class' 'trait' 'composition' 'definition' 'update' ) ]. - toolName = 'class_side_traits_update' ifTrue: [ - ^ #( 'class' 'trait' 'side' 'metaclass' 'composition' 'definition' - 'update' ) ]. - toolName = 'class_shared_variables_update' ifTrue: [ - ^ #( 'class' 'variable' 'shared' 'classvar' 'binding' 'update' ) ]. - toolName = 'class_shared_pools_update' ifTrue: [ - ^ #( 'class' 'pool' 'shared' 'pooldictionary' 'dictionary' - 'update' ) ]. - toolName = 'class_layout_update' ifTrue: [ - ^ #( 'class' 'layout' 'shape' 'format' 'fixed' 'variable' - 'weak' 'immediate' 'word' 'byte' 'update' ) ]. - toolName = 'class_slot_add' ifTrue: [ - ^ #( 'class' 'slot' 'add' 'refactor' 'refactoring' ) ]. - toolName = 'class_slot_remove' ifTrue: [ - ^ #( 'class' 'slot' 'remove' 'refactor' 'refactoring' ) ]. - toolName = 'class_slot_name_update' ifTrue: [ - ^ #( 'class' 'slot' 'name' 'rename' 'update' 'refactor' 'refactoring' ) ]. - toolName = 'class_slot_pull_up' ifTrue: [ - ^ #( 'class' 'slot' 'pull' 'up' 'refactor' 'refactoring' ) ]. - toolName = 'class_slot_push_down' ifTrue: [ - ^ #( 'class' 'slot' 'push' 'down' 'refactor' 'refactoring' ) ]. - toolName = 'class_remove' ifTrue: [ - ^ #( 'class' 'remove' 'refactor' 'refactoring' ) ]. - toolName = 'method_metadata_search' ifTrue: [ - ^ #( 'method' 'selector' 'protocol' 'metadata' 'search' ) ]. - toolName = 'method_source_search' ifTrue: [ - ^ #( 'method' 'source' 'code' 'text' 'search' ) ]. - toolName = 'method_equivalent_search' ifTrue: [ - ^ #( 'method' 'equivalent' 'ast' 'duplicate' 'semantic' 'search' ) ]. - toolName = 'method_implementor_search' ifTrue: [ - ^ #( 'method' 'selector' 'implementor' 'implementation' ) ]. - toolName = 'method_sender_search' ifTrue: [ - ^ #( 'method' 'selector' 'sender' 'send' 'reference' ) ]. - toolName = 'method_class_reference_search' ifTrue: [ - ^ #( 'method' 'class' 'reference' 'binding' ) ]. - toolName = 'method_variable_reference_search' ifTrue: [ - ^ #( 'method' 'variable' 'reference' 'slot' 'temporary' 'classvar' - 'binding' ) ]. - toolName = 'method_get' ifTrue: [ - ^ #( 'method' 'selector' 'protocol' 'source' 'reference' 'variable' ) ]. - toolName = 'method_compile' ifTrue: [ - ^ #( 'method' 'source' 'protocol' 'create' 'compile' ) ]. - toolName = 'method_selector_update' ifTrue: [ - ^ #( 'method' 'selector' 'signature' 'rename' 'update' 'refactor' - 'refactoring' ) ]. - toolName = 'method_protocol_update' ifTrue: [ - ^ #( 'method' 'protocol' 'category' 'update' ) ]. - toolName = 'method_rewrite' ifTrue: [ - ^ #( 'method' 'rewrite' 'preview' 'apply' 'ast' 'pattern' 'refactor' - 'refactoring' ) ]. - toolName = 'test_run' ifTrue: [ ^ #( 'test' 'sunit' 'timeout' ) ]. - toolName = 'test_coverage_run' ifTrue: [ - ^ #( 'test' 'sunit' 'coverage' 'timeout' ) ]. - toolName = 'history_file_list' ifTrue: [ - ^ #( 'history' 'change' 'file' 'list' 'ombu' 'epicea' ) ]. - toolName = 'history_entry_list' ifTrue: [ - ^ #( 'history' 'change' 'entry' 'browse' 'list' 'inspect' 'ombu' - 'epicea' ) ]. - toolName = 'history_entry_apply' ifTrue: [ - ^ #( 'history' 'change' 'entry' 'preview' 'apply' 'recover' - 'epicea' 'ombu' ) ]. - toolName = 'history_entry_revert' ifTrue: [ - ^ #( 'history' 'change' 'entry' 'preview' 'revert' 'recover' - 'epicea' 'ombu' ) ]. - toolName = 'screenshot_capture' ifTrue: [ - ^ #( 'screenshot' 'capture' 'window' 'display' 'world' 'ui' ) ]. - toolName = 'image_evaluate' ifTrue: [ - ^ #( 'expression' 'smalltalk' 'code' ) ]. - (toolName beginsWith: 'debug_') ifTrue: [ - ^ #( 'debug' 'session' 'state' 'frame' 'variable' 'breakpoint' - 'capture' 'control' 'repair' 'exception' ) ]. - ^ #( ) -] - -{ #category : 'tools' } -MCPToolRegistry class >> contractForTool: aTool [ - - ^ self - contractForTool: aTool - usingPolicy: MCPToolExposurePolicy default -] - -{ #category : 'tools' } -MCPToolRegistry class >> contractForTool: aTool usingPolicy: aToolExposurePolicy [ - - ^ self - contractForTool: aTool - usingPolicy: aToolExposurePolicy - includeOutputSchema: true -] - -{ #category : 'tools' } -MCPToolRegistry class >> contractForTool: aTool usingPolicy: aToolExposurePolicy includeOutputSchema: includeOutputSchema [ - - | activationRequirements data examples | - data := (self metadataForTool: aTool usingPolicy: aToolExposurePolicy) - copy. - data at: #inputSchema put: aTool inputSchema asJRPCJSON. - includeOutputSchema ifTrue: [ - data at: #outputSchema put: aTool outputSchema asJRPCJSON ]. - examples := self examplesForTool: aTool. - examples ifNotEmpty: [ data at: #examples put: examples ]. - activationRequirements := self activationRequirementsForTool: aTool. - activationRequirements ifNotEmpty: [ - data at: #activationRequirements put: activationRequirements ]. - ^ data + | tool | + tool := self toolNamed: aToolName ifAbsent: [ Error signal: 'Unknown MCP tool: ' , aToolName asString ]. + ^ self contractForTool: tool includeOutputSchema: includeOutputSchema ] { #category : 'registrations' } -MCPToolRegistry class >> defaultExposureForToolClass: aToolClass [ - - ^ (self staticToolClasses includes: aToolClass) - ifTrue: [ 'static' ] - ifFalse: [ 'discoverable' ] -] - -{ #category : 'tools' } -MCPToolRegistry class >> defaultPublicToolGroups [ - - ^ { - ('tool-catalog' -> { - MCPToolSearchTools. - MCPToolGetTool. - MCPToolCallTool }). - ('repositories' -> { - MCPToolSearchRepositories. - MCPToolVerifyRepositoryIdentity. - MCPToolListRepositoryChanges. - MCPToolDiscardRepositoryChanges. - MCPToolCreateRepository. - MCPToolAttachRepository. - MCPToolUpdateRepository. - MCPToolAddRepositoryRemote. - MCPToolRemoveRepositoryRemote. - MCPToolUpdateRepositoryRemote. - - MCPToolExportRepository. - MCPToolCommitRepository. - MCPToolFetchRepository. - MCPToolPullRepository. - MCPToolPushRepository. - MCPToolCreateRepositoryBranch. - MCPToolSwitchRepositoryBranch. - MCPToolCheckoutRepositoryBranch. - MCPToolAdoptRepositoryHead. - MCPToolLoadBaseline. - MCPToolLoadRepository }). - ('packages' -> { MCPToolSearchPackages }). - ('classes' -> { - MCPToolSearchClasses. - MCPToolGetClass. - MCPToolCreateClass. - MCPToolUpdateClassName. - MCPToolUpdateClassSuperclass. - MCPToolUpdateClassPackage. - MCPToolUpdateClassComment. - MCPToolUpdateClassSlots. - MCPToolUpdateClassTraits. - MCPToolUpdateClassSideTraits. - MCPToolUpdateClassSharedVariables. - MCPToolUpdateClassSharedPools. - MCPToolUpdateClassLayout. - MCPToolAddClassSlot. - MCPToolRemoveClassSlot. - MCPToolUpdateClassSlotName. - MCPToolPullUpClassSlot. - MCPToolPushDownClassSlot. - MCPToolRemoveClasses }). - ('methods' -> { - MCPToolSearchMethodMetadata. - MCPToolSearchMethodSource. - MCPToolSearchEquivalentMethods. - MCPToolSearchMethodImplementors. - MCPToolSearchMethodSenders. - MCPToolSearchClassReferences. - MCPToolSearchVariableReferences. - MCPToolGetMethod. - MCPToolCompileMethod. - MCPToolUpdateMethodSelector. - MCPToolUpdateMethodProtocol. - MCPToolRewriteMethods. - MCPToolRemoveMethods }). - ('history' -> { - MCPToolListChangeHistoryFiles. - MCPToolListChangeHistoryEntries. - MCPToolApplyChangeHistoryEntries. - MCPToolRevertChangeHistoryEntries }). - ('tests' -> { - MCPToolRunTests. - MCPToolRunTestCoverage }). - ('ui' -> { MCPToolCaptureScreenshot }). - ('debugging' -> { - MCPToolListDebugSessions. - MCPToolGetDebugSession. - MCPToolRemoveDebugSession. - MCPToolSearchDebugCandidates. - MCPToolAttachDebugCandidate. - MCPToolDebugState. - MCPToolDebugVariables. - MCPToolDebugEvaluate. - MCPToolStepIntoDebugSession. - MCPToolStepOverDebugSession. - MCPToolStepThroughDebugSession. - MCPToolRestartDebugSession. - MCPToolResumeDebugSession. - MCPToolTerminateDebugSession. - MCPToolListDebugBreakpoints. - MCPToolSetDebugBreakpoint. - MCPToolRemoveDebugBreakpoint. - MCPToolEnableDebugBreakpoint. - MCPToolDisableDebugBreakpoint. - MCPToolClearDebugBreakpoints. - MCPToolDebugCapture. - MCPToolDebugTest. - MCPToolUpdateDebugMethod }). - ('runtime' -> { MCPToolEvaluate }) } -] - -{ #category : 'registrations' } -MCPToolRegistry class >> defaultRegistrations [ - - ^ Array streamContents: [ :stream | - self defaultPublicToolGroups do: [ :group | - group value do: [ :toolClass | - stream nextPut: (MCPToolRegistration - tool: toolClass new - groupName: group key - defaultExposure: - (self defaultExposureForToolClass: toolClass)) ] ] ] -] - -{ #category : 'tools' } -MCPToolRegistry class >> defaultStaticToolNames [ - - ^ (self registrations - select: [ :each | each isStaticByDefault ] - thenCollect: [ :each | each name ]) asArray -] +MCPToolRegistry >> defaultRegistrations [ -{ #category : 'tools' } -MCPToolRegistry class >> examplesForTool: aTool [ - - ^ #( ) + | tools | + tools := (MCPTool concreteSubclasses + select: [ :toolClass | toolClass isBuiltIn ] + thenCollect: [ :toolClass | toolClass newForRegistry: self ]) asArray. + tools sort: [ :left :right | + left groupName = right groupName + ifTrue: [ left name <= right name ] + ifFalse: [ left groupName <= right groupName ] ]. + ^ tools collect: [ :tool | MCPToolRegistration tool: tool ] ] { #category : 'tools' } -MCPToolRegistry class >> exposureForTool: aTool [ +MCPToolRegistry >> exposureForTool: aTool [ - ^ self - exposureForTool: aTool - usingPolicy: MCPToolExposurePolicy default + | registration | + registration := self registrationForTool: aTool ifAbsent: [ Error signal: 'MCP tool is not registered: ' , aTool name ]. + ^ self exposurePolicy exposureForToolRegistration: registration ] -{ #category : 'tools' } -MCPToolRegistry class >> exposureForTool: aTool usingPolicy: aToolExposurePolicy [ +{ #category : 'accessing' } +MCPToolRegistry >> exposurePolicy [ - ^ aToolExposurePolicy exposureForToolRegistration: - (self registrationForTool: aTool ifAbsent: [ ^ 'discoverable' ]) + ^ exposurePolicy ] -{ #category : 'tools' } -MCPToolRegistry class >> groupNameForToolClass: aToolClass [ +{ #category : 'accessing' } +MCPToolRegistry >> exposurePolicy: aToolExposurePolicy [ - ^ (self registrationForToolClass: aToolClass ifAbsent: [ ^ 'other' ]) - groupName + self validateExposurePolicy: aToolExposurePolicy. + exposurePolicy := aToolExposurePolicy ] { #category : 'testing' } -MCPToolRegistry class >> includesToolNamed: aToolName [ +MCPToolRegistry >> includesToolNamed: aToolName [ ^ self publicToolNames includes: aToolName asString ] { #category : 'initialization' } -MCPToolRegistry class >> initialize [ +MCPToolRegistry >> initialize [ + super initialize. + exposurePolicy := MCPToolExposurePolicy default. self resetRegistrations ] { #category : 'testing' } -MCPToolRegistry class >> isStaticTool: aToolName [ - - ^ self - isStaticTool: aToolName - usingPolicy: MCPToolExposurePolicy default -] - -{ #category : 'testing' } -MCPToolRegistry class >> isStaticTool: aToolName usingPolicy: aToolExposurePolicy [ - - ^ aToolExposurePolicy isStaticTool: aToolName -] - -{ #category : 'tools' } -MCPToolRegistry class >> keywordsForTool: aTool [ - - | keywords normalizedKeywords | - keywords := OrderedCollection new. - keywords addAll: (aTool name findTokens: '-_'). - keywords addAll: (aTool title findTokens: ' -'). - keywords addAll: - ((self groupNameForToolClass: aTool class) findTokens: '-_'). - keywords addAll: (self additionalKeywordsForTool: aTool). - normalizedKeywords := OrderedCollection new. - keywords do: [ :each | - normalizedKeywords addAll: (each asString findTokens: '-_ ') ]. - ^ (normalizedKeywords collect: [ :each | each asLowercase ]) asSet - asArray -] - -{ #category : 'tools' } -MCPToolRegistry class >> metadataForTool: aTool [ +MCPToolRegistry >> isStaticToolNamed: aToolName [ - ^ self - metadataForTool: aTool - usingPolicy: MCPToolExposurePolicy default -] - -{ #category : 'tools' } -MCPToolRegistry class >> metadataForTool: aTool usingPolicy: aToolExposurePolicy [ - - ^ { - (#name -> aTool name). - (#description -> aTool description) } asDictionary -] - -{ #category : 'tools' } -MCPToolRegistry class >> mutabilityForTool: aTool [ - - | executeTools readTools toolName | - toolName := aTool name. - readTools := #( 'tool_search' 'tool_get' 'repository_search' - 'repository_identity_verify' - 'repository_change_list' 'package_search' - 'class_search' 'class_get' 'method_metadata_search' - 'method_source_search' 'method_equivalent_search' - 'method_implementor_search' - 'method_sender_search' 'method_class_reference_search' - 'method_variable_reference_search' - 'method_get' 'history_file_list' 'history_entry_list' - 'screenshot_capture' 'debug_session_list' - 'debug_session_get' 'debug_candidate_search' - 'debug_state_get' 'debug_variable_get' - 'debug_breakpoint_list' ). - executeTools := #( 'tool_call' 'test_run' 'test_coverage_run' - 'image_evaluate' 'debug_expression_evaluate' - 'debug_capture' 'debug_test_run' ). - (readTools includes: toolName) ifTrue: [ ^ 'read' ]. - (executeTools includes: toolName) ifTrue: [ ^ 'execute' ]. - ^ 'write' + | registration | + registration := self registrationNamed: aToolName ifAbsent: [ ^ false ]. + ^ self exposurePolicy isStaticToolRegistration: registration ] { #category : 'tools' } -MCPToolRegistry class >> publicToolClasses [ +MCPToolRegistry >> publicToolClasses [ ^ (self registrations collect: [ :each | each toolClass ]) asArray ] { #category : 'tools' } -MCPToolRegistry class >> publicToolGroups [ +MCPToolRegistry >> publicToolGroups [ | groups | groups := OrderedCollection new. self registrations do: [ :registration | | group | - group := groups - detect: [ :each | each key = registration groupName ] - ifNone: [ - groups add: - registration groupName -> OrderedCollection new. - groups last ]. + group := groups detect: [ :each | each key = registration groupName ] ifNone: [ + groups add: registration groupName -> OrderedCollection new. + groups last ]. group value add: registration toolClass ]. - ^ (groups collect: [ :each | each key -> each value asArray ]) - asArray + ^ (groups collect: [ :each | each key -> each value asArray ]) asArray ] { #category : 'tools' } -MCPToolRegistry class >> publicToolNames [ +MCPToolRegistry >> publicToolNames [ ^ (self registrations collect: [ :each | each name ]) asArray ] { #category : 'tools' } -MCPToolRegistry class >> publicTools [ +MCPToolRegistry >> publicTools [ ^ self registrations collect: [ :each | each tool ] ] { #category : 'tools' } -MCPToolRegistry class >> publicToolsUsingPolicy: aToolExposurePolicy [ - - ^ self publicTools -] - -{ #category : 'tools' } -MCPToolRegistry class >> queryTermAlternativesFor: aTerm [ +MCPToolRegistry >> queryTermAlternativesFor: aTerm [ | alternatives singular term | term := aTerm asString asLowercase. @@ -491,265 +133,140 @@ MCPToolRegistry class >> queryTermAlternativesFor: aTerm [ ] { #category : 'tools' } -MCPToolRegistry class >> queryTermsFrom: queryString [ +MCPToolRegistry >> queryTermsFrom: queryString [ | normalized | - normalized := queryString - ifNil: [ ^ #( ) ] - ifNotNil: [ :value | - value asString trimBoth asLowercase ]. + normalized := queryString ifNil: [ ^ #( ) ] ifNotNil: [ :value | value asString trimBoth asLowercase ]. normalized isEmpty ifTrue: [ ^ #( ) ]. ^ normalized findTokens: ' -_' ] { #category : 'registrations' } -MCPToolRegistry class >> registerTool: aTool groupName: aGroupName defaultExposure: anExposure [ +MCPToolRegistry >> registerTool: aTool [ | registration | - registration := MCPToolRegistration - tool: aTool - groupName: aGroupName - defaultExposure: anExposure. - (self includesToolNamed: registration name) ifTrue: [ - Error signal: 'MCP tool already registered: ' , registration name ]. + registration := MCPToolRegistration tool: aTool. + (self includesToolNamed: registration name) ifTrue: [ Error signal: 'MCP tool already registered: ' , registration name ]. registrations := self registrations copyWith: registration. ^ registration ] { #category : 'registrations' } -MCPToolRegistry class >> registerToolClass: aToolClass groupName: aGroupName defaultExposure: anExposure [ +MCPToolRegistry >> registerToolClass: aToolClass [ - ^ self - registerTool: aToolClass new - groupName: aGroupName - defaultExposure: anExposure + ^ self registerTool: (aToolClass newForRegistry: self) ] { #category : 'registrations' } -MCPToolRegistry class >> registrationForTool: aTool ifAbsent: absentBlock [ +MCPToolRegistry >> registrationForTool: aTool ifAbsent: absentBlock [ ^ self registrationNamed: aTool name ifAbsent: absentBlock ] { #category : 'registrations' } -MCPToolRegistry class >> registrationForToolClass: aToolClass ifAbsent: absentBlock [ +MCPToolRegistry >> registrationForToolClass: aToolClass ifAbsent: absentBlock [ - ^ self registrations - detect: [ :each | each toolClass = aToolClass ] - ifNone: absentBlock + ^ self registrations detect: [ :each | each toolClass = aToolClass ] ifNone: absentBlock ] { #category : 'registrations' } -MCPToolRegistry class >> registrationNamed: aToolName ifAbsent: absentBlock [ +MCPToolRegistry >> registrationNamed: aToolName ifAbsent: absentBlock [ | toolName | toolName := aToolName asString. - ^ self registrations - detect: [ :each | each name = toolName ] - ifNone: absentBlock + ^ self registrations detect: [ :each | each name = toolName ] ifNone: absentBlock ] { #category : 'registrations' } -MCPToolRegistry class >> registrations [ +MCPToolRegistry >> registrations [ - ^ registrations ifNil: [ registrations := self defaultRegistrations ] + ^ registrations ] { #category : 'registrations' } -MCPToolRegistry class >> resetRegistrations [ - - registrations := nil -] +MCPToolRegistry >> resetRegistrations [ -{ #category : 'tools' } -MCPToolRegistry class >> riskForTool: aTool [ - - | lowRisk mediumRisk toolName | - toolName := aTool name. - lowRisk := #( 'tool_search' 'tool_get' 'repository_search' - 'repository_identity_verify' - 'repository_change_list' 'package_search' - 'class_search' 'class_get' 'method_metadata_search' - 'method_source_search' 'method_equivalent_search' - 'method_implementor_search' 'method_sender_search' - 'method_class_reference_search' - 'method_variable_reference_search' - 'method_get' 'history_file_list' 'history_entry_list' - 'screenshot_capture' 'debug_session_list' - 'debug_session_get' 'debug_candidate_search' - 'debug_state_get' 'debug_variable_get' - 'debug_breakpoint_list' ). - mediumRisk := #( 'tool_call' 'baseline_load' 'repository_load' - 'test_run' 'test_coverage_run' 'image_evaluate' - 'debug_expression_evaluate' - 'debug_capture' 'debug_test_run' ). - (lowRisk includes: toolName) ifTrue: [ ^ 'low' ]. - (mediumRisk includes: toolName) ifTrue: [ ^ 'medium' ]. - ^ 'high' + registrations := self defaultRegistrations ] { #category : 'tools' } -MCPToolRegistry class >> searchMetadataForTool: aTool usingPolicy: aToolExposurePolicy [ - - ^ { - (#name -> aTool name). - (#description -> aTool description) } asDictionary -] - -{ #category : 'tools' } -MCPToolRegistry class >> searchableTextForTool: aTool [ - - ^ String streamContents: [ :stream | - stream - nextPutAll: aTool name; - space; - nextPutAll: aTool title; - space; - nextPutAll: aTool description; - space; - nextPutAll: (self groupNameForToolClass: aTool class). - (self keywordsForTool: aTool) do: [ :keyword | - stream - space; - nextPutAll: keyword ] ] -] - -{ #category : 'tools' } -MCPToolRegistry class >> singularSearchTermFor: aTerm [ +MCPToolRegistry >> singularSearchTermFor: aTerm [ | term | term := aTerm asString asLowercase. - term = 'classes' ifTrue: [ ^ 'class' ]. - term = 'repositories' ifTrue: [ ^ 'repository' ]. - term = 'histories' ifTrue: [ ^ 'history' ]. (term endsWith: 'ies') ifTrue: [ ^ (term allButLast: 3) , 'y' ]. (term endsWith: 'ses') ifTrue: [ ^ term allButLast: 2 ]. - ((term endsWith: 's') and: [ (term endsWith: 'ss') not ]) ifTrue: [ - ^ term allButLast ]. + ((term endsWith: 's') and: [ (term endsWith: 'ss') not ]) ifTrue: [ ^ term allButLast ]. ^ term ] -{ #category : 'accessing' } -MCPToolRegistry class >> staticToolClasses [ - - ^ { - MCPToolSearchTools. - MCPToolGetTool. - MCPToolCallTool. - MCPToolSearchPackages. - MCPToolSearchClasses. - MCPToolGetClass. - MCPToolCreateClass. - MCPToolSearchMethodMetadata. - MCPToolSearchMethodSource. - MCPToolSearchMethodImplementors. - MCPToolSearchMethodSenders. - MCPToolGetMethod. - MCPToolCompileMethod. - MCPToolUpdateMethodSelector. - MCPToolEvaluate. - MCPToolRunTests } -] - { #category : 'tools' } -MCPToolRegistry class >> staticToolNames [ +MCPToolRegistry >> staticToolNames [ - ^ self defaultStaticToolNames + ^ (self staticTools collect: [ :each | each name ]) asArray ] -{ #category : 'tools' } -MCPToolRegistry class >> staticTools [ +{ #category : 'configuration' } +MCPToolRegistry >> staticToolNames: aCollection [ - ^ self staticToolsUsingPolicy: MCPToolExposurePolicy default + self exposurePolicy: (MCPToolExposurePolicy staticToolNames: aCollection) ] { #category : 'tools' } -MCPToolRegistry class >> staticToolsDictionary [ +MCPToolRegistry >> staticTools [ - ^ self staticToolsDictionaryUsingPolicy: - MCPToolExposurePolicy default + ^ (self registrations select: [ :each | self exposurePolicy isStaticToolRegistration: each ] thenCollect: [ :each | each tool ]) + asArray ] { #category : 'tools' } -MCPToolRegistry class >> staticToolsDictionaryUsingPolicy: aToolExposurePolicy [ +MCPToolRegistry >> staticToolsDictionary [ | dictionary | dictionary := Dictionary new. - (self staticToolsUsingPolicy: aToolExposurePolicy) do: [ :tool | - dictionary at: tool name put: tool ]. + self staticTools do: [ :tool | dictionary at: tool name put: tool ]. ^ dictionary ] { #category : 'tools' } -MCPToolRegistry class >> staticToolsUsingPolicy: aToolExposurePolicy [ - - ^ (self registrations - select: [ :each | - aToolExposurePolicy isStaticToolRegistration: each ] - thenCollect: [ :each | each tool ]) asArray -] - -{ #category : 'tools' } -MCPToolRegistry class >> tool: aTool matchesQuery: queryString group: groupString [ - - ^ self - tool: aTool - matchesQuery: queryString - group: groupString - usingPolicy: MCPToolExposurePolicy default -] - -{ #category : 'tools' } -MCPToolRegistry class >> tool: aTool matchesQuery: queryString group: groupString usingPolicy: aToolExposurePolicy [ +MCPToolRegistry >> tool: aTool matchesQuery: queryString group: groupString [ | group queryTerms searchableText | - group := groupString ifNotNil: [ :value | - value asString trimBoth asLowercase ]. - (group isNotNil and: [ - group isNotEmpty and: [ - group ~= (self groupNameForToolClass: aTool class) ] ]) ifTrue: [ - ^ false ]. + group := groupString ifNotNil: [ :value | value asString trimBoth asLowercase ]. + (group isNotNil and: [ group isNotEmpty and: [ group ~= aTool groupName ] ]) ifTrue: [ ^ false ]. queryTerms := self queryTermsFrom: queryString. queryTerms ifEmpty: [ ^ true ]. - searchableText := (self searchableTextForTool: aTool) asLowercase. + searchableText := aTool searchableText asLowercase. ^ queryTerms anySatisfy: [ :each | - (self queryTermAlternativesFor: each) anySatisfy: [ :alternative | - searchableText includesSubstring: alternative ] ] -] - -{ #category : 'tools' } -MCPToolRegistry class >> toolNamed: aToolName ifAbsent: absentBlock [ - - ^ self - toolNamed: aToolName - ifAbsent: absentBlock - usingPolicy: MCPToolExposurePolicy default + (self queryTermAlternativesFor: each) anySatisfy: [ :alternative | searchableText includesSubstring: alternative ] ] ] { #category : 'tools' } -MCPToolRegistry class >> toolNamed: aToolName ifAbsent: absentBlock usingPolicy: aToolExposurePolicy [ +MCPToolRegistry >> toolNamed: aToolName ifAbsent: absentBlock [ - ^ (self - registrationNamed: aToolName - ifAbsent: [ ^ absentBlock value ]) tool + ^ (self registrationNamed: aToolName ifAbsent: [ ^ absentBlock value ]) tool ] { #category : 'tools' } -MCPToolRegistry class >> toolsMatchingQuery: queryString group: groupString usingPolicy: aToolExposurePolicy [ +MCPToolRegistry >> toolsMatchingQuery: queryString group: groupString [ - ^ (self publicTools select: [ :tool | - self - tool: tool - matchesQuery: queryString - group: groupString - usingPolicy: aToolExposurePolicy ]) asArray + ^ (self publicTools select: [ :tool | self tool: tool matchesQuery: queryString group: groupString ]) asArray ] { #category : 'registrations' } -MCPToolRegistry class >> unregisterToolNamed: aToolName [ +MCPToolRegistry >> unregisterToolNamed: aToolName [ | toolName | toolName := aToolName asString. - registrations := self registrations reject: [ :each | - each name = toolName ] + registrations := self registrations reject: [ :each | each name = toolName ] +] + +{ #category : 'private - exposure' } +MCPToolRegistry >> validateExposurePolicy: aToolExposurePolicy [ + + | staticNames unknown | + staticNames := aToolExposurePolicy explicitStaticToolNames. + staticNames ifNil: [ ^ self ]. + unknown := staticNames reject: [ :each | self includesToolNamed: each ]. + unknown ifNotEmpty: [ Error signal: 'Unknown MCP tool(s) in exposure policy: ' , unknown printString ] ] diff --git a/src/MCP/MCPToolRemoveClasses.class.st b/src/MCP/MCPToolRemoveClasses.class.st index 56583c2..7bf8372 100644 --- a/src/MCP/MCPToolRemoveClasses.class.st +++ b/src/MCP/MCPToolRemoveClasses.class.st @@ -11,6 +11,12 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolRemoveClasses class >> groupName [ + + ^ self classesGroupName +] + { #category : 'metadata' } MCPToolRemoveClasses class >> toolName [ diff --git a/src/MCP/MCPToolRemoveMethods.class.st b/src/MCP/MCPToolRemoveMethods.class.st index 10506e3..69fb178 100644 --- a/src/MCP/MCPToolRemoveMethods.class.st +++ b/src/MCP/MCPToolRemoveMethods.class.st @@ -11,6 +11,12 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolRemoveMethods class >> groupName [ + + ^ self methodsGroupName +] + { #category : 'metadata' } MCPToolRemoveMethods class >> toolName [ diff --git a/src/MCP/MCPToolRepositoryOperation.class.st b/src/MCP/MCPToolRepositoryOperation.class.st index ab1b9e9..eb1fbaf 100644 --- a/src/MCP/MCPToolRepositoryOperation.class.st +++ b/src/MCP/MCPToolRepositoryOperation.class.st @@ -11,12 +11,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolRepositoryOperation class >> groupName [ + + ^ self repositoriesGroupName +] + { #category : 'testing' } MCPToolRepositoryOperation class >> isAbstract [ ^ self = MCPToolRepositoryOperation ] +{ #category : 'metadata' } +MCPToolRepositoryOperation >> additionalKeywords [ + + ^ #( 'repository' 'repo' 'iceberg' 'git' ) +] + { #category : 'private - schema' } MCPToolRepositoryOperation >> branchNameSchemaProperty [ diff --git a/src/MCP/MCPToolRewriteMethods.class.st b/src/MCP/MCPToolRewriteMethods.class.st index 24efe39..83bf796 100644 --- a/src/MCP/MCPToolRewriteMethods.class.st +++ b/src/MCP/MCPToolRewriteMethods.class.st @@ -12,6 +12,12 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolRewriteMethods class >> groupName [ + + ^ self methodsGroupName +] + { #category : 'metadata' } MCPToolRewriteMethods class >> toolName [ diff --git a/src/MCP/MCPToolRunTestCoverage.class.st b/src/MCP/MCPToolRunTestCoverage.class.st index 5bd3bc5..08f4be0 100644 --- a/src/MCP/MCPToolRunTestCoverage.class.st +++ b/src/MCP/MCPToolRunTestCoverage.class.st @@ -43,6 +43,12 @@ MCPToolRunTestCoverage >> buildOutputSchema [ required: #( 'runCount' 'passedCount' 'coverage' ) ] +{ #category : 'metadata' } +MCPToolRunTestCoverage >> defaultExposure [ + + ^ 'discoverable' +] + { #category : 'metadata' } MCPToolRunTestCoverage >> description [ diff --git a/src/MCP/MCPToolRunTests.class.st b/src/MCP/MCPToolRunTests.class.st index 4da250a..8884d00 100644 --- a/src/MCP/MCPToolRunTests.class.st +++ b/src/MCP/MCPToolRunTests.class.st @@ -13,12 +13,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolRunTests class >> groupName [ + + ^ self testsGroupName +] + { #category : 'metadata' } MCPToolRunTests class >> toolName [ ^ 'test_run' ] +{ #category : 'metadata' } +MCPToolRunTests >> additionalKeywords [ + + ^ #( 'test' 'sunit' 'timeout' ) +] + { #category : 'metadata' } MCPToolRunTests >> buildInputSchema [ @@ -214,6 +226,12 @@ MCPToolRunTests >> defaultCoverageMethodLimit [ ^ 50 ] +{ #category : 'metadata' } +MCPToolRunTests >> defaultExposure [ + + ^ 'static' +] + { #category : 'defaults' } MCPToolRunTests >> defaultTimeoutSeconds [ diff --git a/src/MCP/MCPToolSearch.class.st b/src/MCP/MCPToolSearch.class.st index 5a936a3..f40a989 100644 --- a/src/MCP/MCPToolSearch.class.st +++ b/src/MCP/MCPToolSearch.class.st @@ -21,6 +21,12 @@ MCPToolSearch >> addScopeLabel: aString to: labels [ (labels includes: aString) ifFalse: [ labels add: aString ] ] +{ #category : 'metadata' } +MCPToolSearch >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'private - schema' } MCPToolSearch >> basicFilterModes [ diff --git a/src/MCP/MCPToolSearchClasses.class.st b/src/MCP/MCPToolSearchClasses.class.st index 0cd02de..0a28adc 100644 --- a/src/MCP/MCPToolSearchClasses.class.st +++ b/src/MCP/MCPToolSearchClasses.class.st @@ -11,6 +11,12 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolSearchClasses class >> groupName [ + + ^ self classesGroupName +] + { #category : 'metadata' } MCPToolSearchClasses class >> toolName [ @@ -122,6 +128,12 @@ MCPToolSearchClasses >> classInfoDataProperties [ description: 'Package tag.') } ] +{ #category : 'metadata' } +MCPToolSearchClasses >> defaultExposure [ + + ^ 'static' +] + { #category : 'metadata' } MCPToolSearchClasses >> description [ diff --git a/src/MCP/MCPToolSearchDebugCandidates.class.st b/src/MCP/MCPToolSearchDebugCandidates.class.st index 03ae38e..fd83fff 100644 --- a/src/MCP/MCPToolSearchDebugCandidates.class.st +++ b/src/MCP/MCPToolSearchDebugCandidates.class.st @@ -15,6 +15,12 @@ MCPToolSearchDebugCandidates class >> toolName [ ^ 'debug_candidate_search' ] +{ #category : 'metadata' } +MCPToolSearchDebugCandidates >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'private - specs' } MCPToolSearchDebugCandidates >> sessionToolSpec [ diff --git a/src/MCP/MCPToolSearchMethodImplementors.class.st b/src/MCP/MCPToolSearchMethodImplementors.class.st index a82449b..9306a3c 100644 --- a/src/MCP/MCPToolSearchMethodImplementors.class.st +++ b/src/MCP/MCPToolSearchMethodImplementors.class.st @@ -15,6 +15,12 @@ MCPToolSearchMethodImplementors class >> toolName [ ^ 'method_implementor_search' ] +{ #category : 'metadata' } +MCPToolSearchMethodImplementors >> defaultExposure [ + + ^ 'static' +] + { #category : 'metadata' } MCPToolSearchMethodImplementors >> description [ diff --git a/src/MCP/MCPToolSearchMethodMetadata.class.st b/src/MCP/MCPToolSearchMethodMetadata.class.st index e10ebb7..db1d57f 100644 --- a/src/MCP/MCPToolSearchMethodMetadata.class.st +++ b/src/MCP/MCPToolSearchMethodMetadata.class.st @@ -36,6 +36,12 @@ MCPToolSearchMethodMetadata >> buildInputSchema [ ^ self queryInputSchemaWithProperties: properties ] +{ #category : 'metadata' } +MCPToolSearchMethodMetadata >> defaultExposure [ + + ^ 'static' +] + { #category : 'metadata' } MCPToolSearchMethodMetadata >> description [ diff --git a/src/MCP/MCPToolSearchMethodSenders.class.st b/src/MCP/MCPToolSearchMethodSenders.class.st index 031fc0d..1cfcf8c 100644 --- a/src/MCP/MCPToolSearchMethodSenders.class.st +++ b/src/MCP/MCPToolSearchMethodSenders.class.st @@ -15,6 +15,12 @@ MCPToolSearchMethodSenders class >> toolName [ ^ 'method_sender_search' ] +{ #category : 'metadata' } +MCPToolSearchMethodSenders >> defaultExposure [ + + ^ 'static' +] + { #category : 'metadata' } MCPToolSearchMethodSenders >> description [ diff --git a/src/MCP/MCPToolSearchMethodSource.class.st b/src/MCP/MCPToolSearchMethodSource.class.st index 2ef0677..0124c25 100644 --- a/src/MCP/MCPToolSearchMethodSource.class.st +++ b/src/MCP/MCPToolSearchMethodSource.class.st @@ -39,6 +39,12 @@ MCPToolSearchMethodSource >> buildInputSchema [ yourself ] +{ #category : 'metadata' } +MCPToolSearchMethodSource >> defaultExposure [ + + ^ 'static' +] + { #category : 'private - request' } MCPToolSearchMethodSource >> defaultFilterMode [ diff --git a/src/MCP/MCPToolSearchPackages.class.st b/src/MCP/MCPToolSearchPackages.class.st index ef34db9..2d0edf5 100644 --- a/src/MCP/MCPToolSearchPackages.class.st +++ b/src/MCP/MCPToolSearchPackages.class.st @@ -9,6 +9,12 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolSearchPackages class >> groupName [ + + ^ self packagesGroupName +] + { #category : 'metadata' } MCPToolSearchPackages class >> toolName [ @@ -43,6 +49,12 @@ MCPToolSearchPackages >> buildOutputSchema [ itemNounPhrase: 'package entries' ] +{ #category : 'metadata' } +MCPToolSearchPackages >> defaultExposure [ + + ^ 'static' +] + { #category : 'metadata' } MCPToolSearchPackages >> description [ diff --git a/src/MCP/MCPToolSearchRepositories.class.st b/src/MCP/MCPToolSearchRepositories.class.st index bfbb11e..d268075 100644 --- a/src/MCP/MCPToolSearchRepositories.class.st +++ b/src/MCP/MCPToolSearchRepositories.class.st @@ -11,12 +11,24 @@ Class { #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolSearchRepositories class >> groupName [ + + ^ self repositoriesGroupName +] + { #category : 'metadata' } MCPToolSearchRepositories class >> toolName [ ^ 'repository_search' ] +{ #category : 'metadata' } +MCPToolSearchRepositories >> additionalKeywords [ + + ^ #( 'repository' 'repo' 'iceberg' 'git' 'branch' 'commit' 'remote' 'head' ) +] + { #category : 'metadata' } MCPToolSearchRepositories >> buildInputSchema [ diff --git a/src/MCP/MCPToolSearchTools.class.st b/src/MCP/MCPToolSearchTools.class.st index e646631..fcb67ce 100644 --- a/src/MCP/MCPToolSearchTools.class.st +++ b/src/MCP/MCPToolSearchTools.class.st @@ -1,22 +1,45 @@ " -Static bridge tool for searching the discoverable tool catalog. +Static bridge tool for searching its owning registry's discoverable tool catalog. It is intentionally kept on the fast path so agents can find optional or specialized tools without requiring those tools to be statically advertised. " Class { #name : 'MCPToolSearchTools', #superclass : 'MCPTool', + #instVars : [ + 'toolRegistry' + ], #category : 'MCP-Tools', #package : 'MCP', #tag : 'Tools' } +{ #category : 'metadata' } +MCPToolSearchTools class >> groupName [ + + ^ self toolCatalogGroupName +] + +{ #category : 'instance creation' } +MCPToolSearchTools class >> newForRegistry: aToolRegistry [ + + ^ self new + toolRegistry: aToolRegistry; + yourself +] + { #category : 'metadata' } MCPToolSearchTools class >> toolName [ ^ 'tool_search' ] +{ #category : 'metadata' } +MCPToolSearchTools >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'metadata' } MCPToolSearchTools >> buildInputSchema [ @@ -56,38 +79,30 @@ MCPToolSearchTools >> buildOutputSchema [ { #category : 'private - execution' } MCPToolSearchTools >> commandForRequest: searchRequest [ - ^ MCPSearchToolsCommand tool: self request: searchRequest + ^ MCPSearchToolsCommand tool: self request: searchRequest toolRegistry: self toolRegistry ] { #category : 'metadata' } -MCPToolSearchTools >> description [ +MCPToolSearchTools >> defaultExposure [ - ^ 'Search all MCP-Pharo tools. Use to discover tool names before tool_get or tool_call.' + ^ 'static' ] -{ #category : 'executing' } -MCPToolSearchTools >> executeWithRequest: request [ +{ #category : 'metadata' } +MCPToolSearchTools >> description [ - ^ self - executeWithRequest: request - usingPolicy: MCPToolExposurePolicy default + ^ 'Search all MCP-Pharo tools. Use to discover tool names before tool_get or tool_call.' ] { #category : 'executing' } -MCPToolSearchTools >> executeWithRequest: request usingPolicy: aToolExposurePolicy [ +MCPToolSearchTools >> executeWithRequest: request [ ^ self executeParsedRequestFrom: request do: [ :searchRequest | | data | - data := (MCPSearchToolsCommand - tool: self - request: searchRequest - toolExposurePolicy: aToolExposurePolicy) execute. - self - successResultText: - (data at: #tools) size asString , ' tools found.' - data: data ] + data := (self commandForRequest: searchRequest) execute. + self successResultText: (data at: #tools) size asString , ' tools found.' data: data ] onError: [ :error :parsedRequest | self errorResultFor: error ] ] @@ -123,6 +138,18 @@ MCPToolSearchTools >> toolMetadataObjectSchema [ yourself ] +{ #category : 'accessing' } +MCPToolSearchTools >> toolRegistry [ + + ^ toolRegistry ifNil: [ toolRegistry := MCPToolRegistry new ] +] + +{ #category : 'accessing' } +MCPToolSearchTools >> toolRegistry: aToolRegistry [ + + toolRegistry := aToolRegistry +] + { #category : 'private - schema' } MCPToolSearchTools >> toolsArraySchemaProperty [ diff --git a/src/MCP/MCPToolUpdateClassLayout.class.st b/src/MCP/MCPToolUpdateClassLayout.class.st index 87b24cf..7c13be9 100644 --- a/src/MCP/MCPToolUpdateClassLayout.class.st +++ b/src/MCP/MCPToolUpdateClassLayout.class.st @@ -15,6 +15,12 @@ MCPToolUpdateClassLayout class >> toolName [ ^ 'class_layout_update' ] +{ #category : 'metadata' } +MCPToolUpdateClassLayout >> additionalKeywords [ + + ^ super additionalKeywords , #( 'layout' 'shape' 'format' 'fixed' 'variable' 'weak' 'immediate' 'word' 'byte' ) +] + { #category : 'private - specs' } MCPToolUpdateClassLayout >> classToolSpec [ diff --git a/src/MCP/MCPToolUpdateClassSharedPools.class.st b/src/MCP/MCPToolUpdateClassSharedPools.class.st index 37d97f9..9c7d6e5 100644 --- a/src/MCP/MCPToolUpdateClassSharedPools.class.st +++ b/src/MCP/MCPToolUpdateClassSharedPools.class.st @@ -15,6 +15,12 @@ MCPToolUpdateClassSharedPools class >> toolName [ ^ 'class_shared_pools_update' ] +{ #category : 'metadata' } +MCPToolUpdateClassSharedPools >> additionalKeywords [ + + ^ super additionalKeywords , #( 'pool' 'shared' 'pooldictionary' 'dictionary' ) +] + { #category : 'private - specs' } MCPToolUpdateClassSharedPools >> classToolSpec [ diff --git a/src/MCP/MCPToolUpdateClassSharedVariables.class.st b/src/MCP/MCPToolUpdateClassSharedVariables.class.st index fa7cd0c..287ec9e 100644 --- a/src/MCP/MCPToolUpdateClassSharedVariables.class.st +++ b/src/MCP/MCPToolUpdateClassSharedVariables.class.st @@ -15,6 +15,12 @@ MCPToolUpdateClassSharedVariables class >> toolName [ ^ 'class_shared_variables_update' ] +{ #category : 'metadata' } +MCPToolUpdateClassSharedVariables >> additionalKeywords [ + + ^ super additionalKeywords , #( 'variable' 'shared' 'classvar' 'binding' ) +] + { #category : 'private - specs' } MCPToolUpdateClassSharedVariables >> classToolSpec [ diff --git a/src/MCP/MCPToolUpdateClassSideTraits.class.st b/src/MCP/MCPToolUpdateClassSideTraits.class.st index 4ac1fd2..3598643 100644 --- a/src/MCP/MCPToolUpdateClassSideTraits.class.st +++ b/src/MCP/MCPToolUpdateClassSideTraits.class.st @@ -15,6 +15,12 @@ MCPToolUpdateClassSideTraits class >> toolName [ ^ 'class_side_traits_update' ] +{ #category : 'metadata' } +MCPToolUpdateClassSideTraits >> additionalKeywords [ + + ^ super additionalKeywords , #( 'trait' 'side' 'metaclass' 'composition' 'definition' ) +] + { #category : 'private - specs' } MCPToolUpdateClassSideTraits >> classToolSpec [ diff --git a/src/MCP/MCPToolUpdateClassTraits.class.st b/src/MCP/MCPToolUpdateClassTraits.class.st index 7cb5e65..0ab4ca6 100644 --- a/src/MCP/MCPToolUpdateClassTraits.class.st +++ b/src/MCP/MCPToolUpdateClassTraits.class.st @@ -15,6 +15,12 @@ MCPToolUpdateClassTraits class >> toolName [ ^ 'class_traits_update' ] +{ #category : 'metadata' } +MCPToolUpdateClassTraits >> additionalKeywords [ + + ^ super additionalKeywords , #( 'trait' 'composition' 'definition' ) +] + { #category : 'private - specs' } MCPToolUpdateClassTraits >> classToolSpec [ diff --git a/src/MCP/MCPToolUpdateMethodSelector.class.st b/src/MCP/MCPToolUpdateMethodSelector.class.st index 2ac167d..d770dc9 100644 --- a/src/MCP/MCPToolUpdateMethodSelector.class.st +++ b/src/MCP/MCPToolUpdateMethodSelector.class.st @@ -15,6 +15,12 @@ MCPToolUpdateMethodSelector class >> toolName [ ^ 'method_selector_update' ] +{ #category : 'metadata' } +MCPToolUpdateMethodSelector >> defaultExposure [ + + ^ 'static' +] + { #category : 'private - specs' } MCPToolUpdateMethodSelector >> methodToolSpec [ diff --git a/src/MCP/MCPToolVerifyRepositoryIdentity.class.st b/src/MCP/MCPToolVerifyRepositoryIdentity.class.st index e861c41..5b25ce3 100644 --- a/src/MCP/MCPToolVerifyRepositoryIdentity.class.st +++ b/src/MCP/MCPToolVerifyRepositoryIdentity.class.st @@ -15,6 +15,18 @@ MCPToolVerifyRepositoryIdentity class >> toolName [ ^ 'repository_identity_verify' ] +{ #category : 'metadata' } +MCPToolVerifyRepositoryIdentity >> additionalKeywords [ + + ^ super additionalKeywords , #( 'identity' 'guard' ) +] + +{ #category : 'metadata' } +MCPToolVerifyRepositoryIdentity >> annotations [ + + ^ MCPStructureToolAnnotations readOnly +] + { #category : 'private - specs' } MCPToolVerifyRepositoryIdentity >> repositoryToolSpec [