Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/co-lleague/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Co-lleague MCP Server

AGI co-worker for the Model Context Protocol. Query screens, run tabular predictions, execute intents, and check agent status — all from any MCP-compatible client (Claude Desktop, Cursor, Windsurf, VS Code Copilot, OpenCode).

## Tools

### `co-lleague_screen_query`
Query what's on the user's screen right now. Returns screen content with AI analysis.

### `co-lleague_tabular_predict`
Run a tabular prediction on input features. Uses TabICL for in-context learning.

### `co-lleague_intent_execute`
Execute a natural-language intent. Describe what you want to do and get results.

### `co-lleague_agent_status`
Get the current agent status — online/offline, active tasks, recent activity.

## Installation

### Claude Desktop
Add to your `claude_desktop_config.json`:
```json
{
"mcpServers": {
"co-lleague": {
"command": "npx",
"args": ["-y", "@aimino/co-lleague-mcp"],
"env": {
"CO_LEAGUE_API_KEY": "your-api-key-here"
}
}
}
}
```

### OpenCode / Cline / Continue.dev
```json
{
"mcpServers": {
"co-lleague": {
"command": "npx",
"args": ["-y", "@aimino/co-lleague-mcp"]
}
}
}
```

## Configuration

| Variable | Required | Default | Description |
|---|---|---|---|
| `CO_LEAGUE_API_KEY` | Yes | — | API key for co-lleague backend |
| `CO_LEAGUE_API_BASE` | No | `https://api.co-lleague.ai` | Backend API base URL |

## Resources

- `co-lleague://agents/{agentId}/status` — Live agent status resource

## License

MIT
9 changes: 9 additions & 0 deletions src/co-lleague/__tests__/server.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { describe, it, expect } from "vitest";

describe("co-lleague MCP server", () => {
it("should export server configuration", () => {
const pkg = require("../package.json");
expect(pkg.name).toBe("@modelcontextprotocol/server-co-lleague");
expect(pkg.bin["mcp-server-co-lleague"]).toBe("dist/index.js");
});
});
197 changes: 197 additions & 0 deletions src/co-lleague/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
#!/usr/bin/env node

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const API_BASE = process.env.CO_LEAGUE_API_BASE || "https://api.co-lleague.ai";
const API_KEY = process.env.CO_LEAGUE_API_KEY || "";
const REQUEST_TIMEOUT_MS = 30_000;

async function apiPost(path: string, body: unknown) {
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (API_KEY) headers["x-api-key"] = API_KEY;

try {
const res = await fetch(`${API_BASE}${path}`, {
method: "POST",
headers,
body: JSON.stringify(body),
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
});
if (!res.ok) {
const text = await res.text();
return { ok: false as const, error: `API ${res.status}: ${text}` };
}
return { ok: true as const, data: await res.json() };
} catch (err) {
return {
ok: false as const,
error: `Backend unreachable: ${(err as Error).message}`,
};
}
}

async function apiGet(path: string) {
const headers: Record<string, string> = {};
if (API_KEY) headers["x-api-key"] = API_KEY;

try {
const res = await fetch(`${API_BASE}${path}`, {
method: "GET",
headers,
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS),
});
if (!res.ok) {
const text = await res.text();
return { ok: false as const, error: `API ${res.status}: ${text}` };
}
return { ok: true as const, data: await res.json() };
} catch (err) {
return {
ok: false as const,
error: `Backend unreachable: ${(err as Error).message}`,
};
}
}

const server = new McpServer({
name: "co-lleague",
version: "0.1.0",
});

server.tool(
"co-lleague_screen_query",
"Query what's on the user's screen. Returns screen content with AI analysis.",
{
query: z.string().describe("Natural language question about the screen content"),
context: z.string().optional().describe("Additional context for the query"),
},
async ({ query, context }) => {
const backend = await apiPost("/api/v1/screen/query", {
query,
context: context || "",
});

if (backend.ok) {
return { content: [{ type: "text" as const, text: JSON.stringify(backend.data, null, 2) }] };
}

return {
content: [
{
type: "text" as const,
text: JSON.stringify({
query,
analysis: "Screen analysis unavailable — backend not reachable",
status: "offline",
}, null, 2),
},
],
};
},
);

server.tool(
"co-lleague_tabular_predict",
"Run a tabular prediction on input features.",
{
features: z
.union([z.array(z.number()), z.string()])
.describe("Input features as an array of numbers or a JSON string"),
target_column: z
.string()
.optional()
.describe("Target column name (for named feature sets)"),
},
async ({ features, target_column }) => {
const parsed = typeof features === "string" ? JSON.parse(features) : features;
const backend = await apiPost("/api/v1/predict", {
features: parsed,
target_column: target_column || undefined,
});

if (backend.ok) {
return { content: [{ type: "text" as const, text: JSON.stringify(backend.data, null, 2) }] };
}

return {
content: [
{
type: "text" as const,
text: JSON.stringify({
result: { value: 0.5, confidence: 0.8 },
note: "Fallback: backend unreachable, used default prediction",
}, null, 2),
},
],
};
},
);

server.tool(
"co-lleague_intent_execute",
"Execute a natural-language intent. Returns execution result.",
{
intent: z.string().describe("Natural language description of what to do"),
context: z.string().optional().describe("JSON string with additional context"),
},
async ({ intent, context }) => {
const parsedCtx = context ? JSON.parse(context) : {};
const backend = await apiPost("/api/v1/intent/execute", {
intent,
context: parsedCtx,
});

if (backend.ok) {
return { content: [{ type: "text" as const, text: JSON.stringify(backend.data, null, 2) }] };
}

return {
content: [
{
type: "text" as const,
text: JSON.stringify({
intent,
result: "Execution unavailable — backend not reachable",
status: "offline",
}, null, 2),
},
],
};
},
);

server.tool(
"co-lleague_agent_status",
"Get the current agent status — online/offline, active tasks, recent activity.",
{
agent_id: z.string().default("default").describe("Agent identifier"),
},
async ({ agent_id }) => {
const backend = await apiGet(`/api/v1/agents/${agent_id}/status`);

if (backend.ok) {
return { content: [{ type: "text" as const, text: JSON.stringify(backend.data, null, 2) }] };
}

return {
content: [
{
type: "text" as const,
text: JSON.stringify({
agent_id,
status: "offline",
active_tasks: 0,
uptime_hours: 0,
}, null, 2),
},
],
};
},
);

const transport = new StdioServerTransport();
await server.connect(transport);
46 changes: 46 additions & 0 deletions src/co-lleague/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "@modelcontextprotocol/server-co-lleague",
"version": "0.1.0",
"description": "MCP server for co-lleague — AGI co-worker: screen analysis, tabular prediction, intent execution",
"license": "MIT",
"mcpName": "io.github.modelcontextprotocol/server-co-lleague",
"author": "Aimino GmbH",
"homepage": "https://co-lleague.ai",
"bugs": "https://github.com/Aimino-Tech/co-lleague/issues",
"repository": {
"type": "git",
"url": "https://github.com/Aimino-Tech/co-lleague.git"
},
"type": "module",
"bin": {
"mcp-server-co-lleague": "dist/index.js"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc && shx chmod +x dist/*.js",
"prepare": "npm run build",
"watch": "tsc --watch",
"test": "vitest run --coverage"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/node": "^22",
"@vitest/coverage-v8": "^4.1.8",
"shx": "^0.3.4",
"typescript": "^5.8.2",
"vitest": "^4.1.8"
},
"keywords": [
"mcp",
"co-lleague",
"ai-agent",
"prediction",
"screen-analysis",
"intent-execution"
]
}
18 changes: 18 additions & 0 deletions src/co-lleague/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": ".",
"moduleResolution": "NodeNext",
"module": "NodeNext"
},
"include": [
"./**/*.ts"
],
"exclude": [
"**/__tests__/**",
"**/*.test.ts",
"**/*.spec.ts",
"vitest.config.ts"
]
}
14 changes: 14 additions & 0 deletions src/co-lleague/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['**/__tests__/**/*.test.ts'],
coverage: {
provider: 'v8',
include: ['**/*.ts'],
exclude: ['**/__tests__/**', '**/dist/**'],
},
},
});
Loading