import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
export class ArrayProcessingServer {
constructor() {
this.server = new Server(
{
name: 'loop-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
this.processingState = {
array: [],
task: '',
currentIndex: 0,
results: [],
isInitialized: false,
batchSize: 1,
};
this.setupHandlers();
}
setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'initialize_array',
description: 'Initialize the array and task to be processed',
inputSchema: {
type: 'object',
properties: {
array: {
type: 'array',
description: 'The array of items to process',
},
task: {
type: 'string',
description: 'The task description to perform on each item',
},
batchSize: {
type: 'number',
description: 'Number of items to process in each batch (default: 1)',
minimum: 1,
default: 1,
},
},
required: ['array', 'task'],
},
},
{
name: 'get_next_item',
description: 'Get the next item to process along with the task',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'get_next_batch',
description: 'Get the next batch of items to process based on the configured batch size',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'store_result',
description: 'Store the result of processing the current item or batch of items',
inputSchema: {
type: 'object',
properties: {
result: {
type: 'any',
description: 'The result of processing the current item or array of results for batch processing',
},
},
required: ['result'],
},
},
{
name: 'get_all_results',
description: 'Get all results after all items have been processed. This tool will return an error if processing is not complete.',
inputSchema: {
type: 'object',
properties: {
summarize: {
type: 'boolean',
description: 'Whether to include a summary of the results',
default: false,
},
},
},
},
{
name: 'reset',
description: 'Reset the processing state',
inputSchema: {
type: 'object',
properties: {},
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case 'initialize_array':
return this.initializeArray(args);
case 'get_next_item':
return this.getNextItem();
case 'get_next_batch':
return this.getNextBatch();
case 'store_result':
return this.storeResult(args);
case 'get_all_results':
return this.getAllResults(args);
case 'reset':
return this.reset();
default:
throw new Error(`Unknown tool: ${name}`);
}
});
}
initializeArray(args) {
const { array, task, batchSize = 1 } = args;
if (!Array.isArray(array) || array.length === 0) {
return {
content: [
{
type: 'text',
text: 'Error: Please provide a non-empty array',
},
],
};
}
this.processingState = {
array,
task,
currentIndex: 0,
results: [],
isInitialized: true,
batchSize,
};
return {
content: [
{
type: 'text',
text: `Array initialized with ${array.length} items. Task: "${task}". Batch size: ${batchSize}`,
},
],
};
}
getNextItem() {
if (!this.processingState.isInitialized) {
return {
content: [
{
type: 'text',
text: 'Error: Array not initialized. Please call initialize_array first.',
},
],
};
}
if (this.processingState.currentIndex >= this.processingState.array.length) {
return {
content: [
{
type: 'text',
text: 'All items have been processed.',
},
],
};
}
const currentItem = this.processingState.array[this.processingState.currentIndex];
const response = {
item: currentItem,
index: this.processingState.currentIndex,
task: this.processingState.task,
total: this.processingState.array.length,
remaining: this.processingState.array.length - this.processingState.currentIndex,
};
return {
content: [
{
type: 'text',
text: JSON.stringify(response, null, 2),
},
],
};
}
getNextBatch() {
if (!this.processingState.isInitialized) {
return {
content: [
{
type: 'text',
text: 'Error: Array not initialized. Please call initialize_array first.',
},
],
};
}
if (this.processingState.currentIndex >= this.processingState.array.length) {
return {
content: [
{
type: 'text',
text: 'All items have been processed.',
},
],
};
}
const startIndex = this.processingState.currentIndex;
const endIndex = Math.min(
startIndex + this.processingState.batchSize,
this.processingState.array.length
);
const batch = this.processingState.array.slice(startIndex, endIndex);
const response = {
items: batch,
startIndex: startIndex,
endIndex: endIndex - 1,
task: this.processingState.task,
total: this.processingState.array.length,
remaining: this.processingState.array.length - startIndex,
batchSize: batch.length,
};
return {
content: [
{
type: 'text',
text: JSON.stringify(response, null, 2),
},
],
};
}
storeResult(args) {
if (!this.processingState.isInitialized) {
return {
content: [
{
type: 'text',
text: 'Error: Array not initialized. Please call initialize_array first.',
},
],
};
}
const { result } = args;
if (this.processingState.currentIndex >= this.processingState.array.length) {
return {
content: [
{
type: 'text',
text: 'Error: No more items to process.',
},
],
};
}
// Handle batch results
if (Array.isArray(result)) {
const batchSize = Math.min(result.length, this.processingState.array.length - this.processingState.currentIndex);
for (let i = 0; i < batchSize; i++) {
this.processingState.results.push({
index: this.processingState.currentIndex + i,
item: this.processingState.array[this.processingState.currentIndex + i],
result: result[i],
});
}
this.processingState.currentIndex += batchSize;
} else {
// Handle single result
this.processingState.results.push({
index: this.processingState.currentIndex,
item: this.processingState.array[this.processingState.currentIndex],
result,
});
this.processingState.currentIndex++;
}
const hasMore = this.processingState.currentIndex < this.processingState.array.length;
return {
content: [
{
type: 'text',
text: `Result stored. ${hasMore ? `${this.processingState.array.length - this.processingState.currentIndex} items remaining.` : 'All items processed.'}`,
},
],
};
}
getAllResults(args) {
if (!this.processingState.isInitialized) {
return {
content: [
{
type: 'text',
text: 'Error: Array not initialized. Please call initialize_array first.',
},
],
};
}
if (this.processingState.currentIndex < this.processingState.array.length) {
return {
content: [
{
type: 'text',
text: `Error: Processing not complete. ${this.processingState.array.length - this.processingState.currentIndex} items remaining.`,
},
],
};
}
const response = {
results: this.processingState.results,
processed: this.processingState.results.length,
total: this.processingState.array.length,
};
if (args?.summarize) {
response.summary = {
totalItems: this.processingState.array.length,
processedItems: this.processingState.results.length,
task: this.processingState.task,
completedAt: new Date().toISOString(),
};
}
return {
content: [
{
type: 'text',
text: JSON.stringify(response, null, 2),
},
],
};
}
reset() {
this.processingState = {
array: [],
task: '',
currentIndex: 0,
results: [],
isInitialized: false,
batchSize: 1,
};
return {
content: [
{
type: 'text',
text: 'Processing state has been reset.',
},
],
};
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('Loop MCP server running on stdio');
}
}
export default ArrayProcessingServer;