Working with GT as an MCP server

GT ships with a minimal implementation of an MCP server that can execute tools, expose resources, and define prompts.

N.B.: This server was neither hardened nor audited. It is a simple Zinc server that might crash or, depending on the capabilities you give the tools it exposes, do undesirable things to your image or system at large. Use with caution at this time.

To start an MCP server on port 3000, execute the following snippet.

server := GtMcpServer new
		port: 3000;
		start
  

To stop the server, you can call GtMcpServer>>#stop stop server ifNotNil: [ server stop. server := nil ] on it.

server stop
  

Once we have a server, we can start by adding tools to it. The tools use the same model as in Adding custom tools to assistants and can thus be shared across assistants and servers.

server
	addTool: (GtLlmFunctionTool new
			name: 'getImplementors';
			parameters: {'methodName'};
			description: 'Gets a method by name and returns a list of methods that implement it.';
			block: [ :functionCall | 
				Character cr
					join: (functionCall anyArgument asSymbol gtImplementors result toArray wait
							collect: #name) ])
  

Once we have this server, we can test it by connecting to it using a client.

client := GtMcpClient new
	transport: (GtMcpHttpTransport new url: 'http://localhost:3000/')
  

We can confirm the presence of the tools, either through navigating to the view on the client, or programmatically.

client listTools
  

Once we’ve confirmed the presence of the tool, we can call it.

client
	callTool: 'getImplementors'
	withArguments: {'methodName' -> 'callTool:withArguments:'} asDictionary
  

Like tools, we can expose resources on the server. We define them using a model and add them to the server. For the purposes of this example, we take a random page from the Glamorous Toolkit Book.

page := LeDatabase gtBook pages atRandom.
pageUri := 'lepage://' , page database properties databaseName , ':' , page title.

server
	addResource: (GtMcpResource new
			name: page title;
			uri: pageUri;
			mimeType: ZnMimeType textPlain;
			contentsBlock: [ GtLlmPageExporter new
					page: page;
					export ])
  

Once again, we can define a client (if you haven’t already).

client := GtMcpClient new
	transport: (GtMcpHttpTransport new url: 'http://localhost:3000/')
  

We can then query for resources

client listResources
  

or get them directly.

(client readResource: pageUri) first at: 'text'
  

Lastly, the server can also generate prompts. Once again, we need to define one and add it to the server.

server
	addPrompt: (GtMcpPrompt new
			name: 'Code Review';
			description: 'Asks the LLM to analyze code quality and suggest improvements';
			arguments: {'code'};
			promptBlock: [ :arguments | 
				{GtLlmUserMessage new
						content: {'type' -> 'text'.
								'text'
									-> ('Review the following code as if you were a senior developer:
			
```
' , (arguments at: 'code')
											, '
```')} asDictionary} ])
  

Once we have the prompt, we can then, again, optionally create a client.

client := GtMcpClient new
	transport: (GtMcpHttpTransport new url: 'http://localhost:3000/')
  

And query for prompts

client listPrompts
  

Or create messages from it directly.

(client
	messagesFromPrompt: 'Code Review'
	withArguments: {'code' -> '(defn a-clojure-fn [x] (* x 2))'} asDictionary)
	first