@agent-collab/mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ export type CollaborationClientEnv = {
2
+ COLLABORATION_API_BASE_URL?: string;
3
+ MCP_API_TOKEN?: string;
4
+ COLLABORATION_CLIENT?: string;
5
+ COLLABORATION_TASK_ID?: string;
6
+ };
7
+ export type JsonObject = Record<string, unknown>;
8
+ export type GetCollaborationContextInput = {
9
+ projectSlug?: string;
10
+ taskId?: string;
11
+ };
12
+ export type GetProjectConflictsInput = {
13
+ projectSlug?: string;
14
+ };
15
+ export type ClaimPathsInput = {
16
+ taskId: string;
17
+ agentSessionId?: string;
18
+ ownerMemberId?: string;
19
+ claims: Array<{
20
+ path: string;
21
+ claimType: "file" | "directory" | "module" | "service";
22
+ }>;
23
+ };
24
+ export type RegisterSessionInput = {
25
+ taskId?: string;
26
+ client: "codex-app" | "codex-cli" | "ide-agent";
27
+ };
28
+ export type HeartbeatInput = {
29
+ agentSessionId: string;
30
+ status?: "online" | "idle" | "offline";
31
+ };
32
+ export type ReportProgressInput = {
33
+ taskId: string;
34
+ agentSessionId?: string;
35
+ kind: "started" | "progress" | "finding" | "submitted" | "blocked";
36
+ message: string;
37
+ };
38
+ export type SubmitWorkInput = {
39
+ taskId: string;
40
+ title: string;
41
+ kind: "branch" | "patch" | "pull_request";
42
+ reference: string;
43
+ };
44
+ export type CollaborationClient = {
45
+ getCollaborationContext: (input: GetCollaborationContextInput) => Promise<unknown>;
46
+ getProjectConflicts: (input: GetProjectConflictsInput) => Promise<unknown>;
47
+ registerSession: (input: RegisterSessionInput) => Promise<unknown>;
48
+ heartbeat: (input: HeartbeatInput) => Promise<unknown>;
49
+ claimPaths: (input: ClaimPathsInput) => Promise<unknown>;
50
+ reportProgress: (input: ReportProgressInput) => Promise<unknown>;
51
+ submitWork: (input: SubmitWorkInput) => Promise<unknown>;
52
+ };
53
+ export type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
54
+ export declare class CollaborationApiError extends Error {
55
+ readonly status?: number | undefined;
56
+ readonly code?: string | undefined;
57
+ constructor(message: string, status?: number | undefined, code?: string | undefined);
58
+ }
59
+ export declare const createCollaborationClient: (env?: CollaborationClientEnv, fetchImpl?: FetchLike) => CollaborationClient;
package/dist/client.js ADDED
@@ -0,0 +1,138 @@
1
+ export class CollaborationApiError extends Error {
2
+ status;
3
+ code;
4
+ constructor(message, status, code) {
5
+ super(message);
6
+ this.status = status;
7
+ this.code = code;
8
+ this.name = "CollaborationApiError";
9
+ }
10
+ }
11
+ export const createCollaborationClient = (env = process.env, fetchImpl = fetch) => {
12
+ let sessionPromise = null;
13
+ const get = (path, query) => requestJson(env, fetchImpl, path, {
14
+ method: "GET",
15
+ query
16
+ });
17
+ const post = (path, body) => requestJson(env, fetchImpl, path, {
18
+ method: "POST",
19
+ body
20
+ });
21
+ const register = async (input) => {
22
+ const response = await post("/api/collaboration/register-session", input);
23
+ const session = readRegisteredSession(response);
24
+ if (session) {
25
+ sessionPromise = Promise.resolve(session);
26
+ }
27
+ return response;
28
+ };
29
+ const ensureSession = (taskId) => {
30
+ sessionPromise ??= register({
31
+ taskId: taskId ?? env.COLLABORATION_TASK_ID,
32
+ client: collaborationClientName(env.COLLABORATION_CLIENT)
33
+ }).then((response) => {
34
+ const session = readRegisteredSession(response);
35
+ if (!session) {
36
+ throw new CollaborationApiError("register-session response did not include session id and member id");
37
+ }
38
+ return session;
39
+ });
40
+ return sessionPromise;
41
+ };
42
+ return {
43
+ getCollaborationContext: (input) => get("/api/collaboration/context", {
44
+ projectSlug: input.projectSlug,
45
+ taskId: input.taskId
46
+ }),
47
+ getProjectConflicts: (input) => get("/api/collaboration/conflicts", {
48
+ projectSlug: input.projectSlug
49
+ }),
50
+ registerSession: register,
51
+ heartbeat: (input) => post("/api/collaboration/heartbeat", input),
52
+ claimPaths: async (input) => {
53
+ const session = input.agentSessionId && input.ownerMemberId
54
+ ? null
55
+ : await ensureSession(input.taskId);
56
+ return post("/api/collaboration/claim-paths", {
57
+ ...input,
58
+ agentSessionId: input.agentSessionId ?? session?.id,
59
+ ownerMemberId: input.ownerMemberId ?? session?.memberId
60
+ });
61
+ },
62
+ reportProgress: async (input) => {
63
+ const session = input.agentSessionId ? null : await ensureSession(input.taskId);
64
+ return post("/api/collaboration/progress", {
65
+ ...input,
66
+ agentSessionId: input.agentSessionId ?? session?.id
67
+ });
68
+ },
69
+ submitWork: (input) => post("/api/collaboration/submit-work", input)
70
+ };
71
+ };
72
+ const collaborationClientName = (value) => {
73
+ if (value === "codex-cli" || value === "ide-agent") {
74
+ return value;
75
+ }
76
+ return "codex-app";
77
+ };
78
+ const readRegisteredSession = (value) => {
79
+ const response = value;
80
+ const id = response.session?.id;
81
+ const memberId = response.session?.memberId;
82
+ if (typeof id === "string" && typeof memberId === "string") {
83
+ return {
84
+ id,
85
+ memberId
86
+ };
87
+ }
88
+ return null;
89
+ };
90
+ const requestJson = async (env, fetchImpl, path, request) => {
91
+ const baseUrl = requiredEnv(env.COLLABORATION_API_BASE_URL, "COLLABORATION_API_BASE_URL");
92
+ const token = requiredEnv(env.MCP_API_TOKEN, "MCP_API_TOKEN");
93
+ const url = buildUrl(baseUrl, path, request.query);
94
+ const response = await fetchImpl(url, {
95
+ method: request.method,
96
+ headers: {
97
+ authorization: `Bearer ${token}`,
98
+ ...(request.body ? { "content-type": "application/json" } : {})
99
+ },
100
+ ...(request.body ? { body: JSON.stringify(request.body) } : {})
101
+ });
102
+ const json = await readJson(response);
103
+ if (!response.ok) {
104
+ const apiError = json;
105
+ const code = apiError.error?.code ?? "unknown_error";
106
+ const message = apiError.error?.message ?? response.statusText;
107
+ throw new CollaborationApiError(`Collaboration API request failed: ${response.status} ${code} - ${message}`, response.status, code);
108
+ }
109
+ return json;
110
+ };
111
+ const requiredEnv = (value, name) => {
112
+ if (!value) {
113
+ throw new CollaborationApiError(`${name} is required`);
114
+ }
115
+ return value;
116
+ };
117
+ const buildUrl = (baseUrl, path, query) => {
118
+ const url = new URL(path, withTrailingSlash(baseUrl));
119
+ for (const [key, value] of Object.entries(query ?? {})) {
120
+ if (value) {
121
+ url.searchParams.set(key, value);
122
+ }
123
+ }
124
+ return url.toString();
125
+ };
126
+ const withTrailingSlash = (value) => (value.endsWith("/") ? value : `${value}/`);
127
+ const readJson = async (response) => {
128
+ const text = await response.text();
129
+ if (!text) {
130
+ return null;
131
+ }
132
+ try {
133
+ return JSON.parse(text);
134
+ }
135
+ catch {
136
+ throw new CollaborationApiError(`Collaboration API returned non-JSON response with status ${response.status}`, response.status);
137
+ }
138
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import * as z from "zod/v4";
5
+ import { createCollaborationClient } from "./client.js";
6
+ const server = new McpServer({
7
+ name: "agent-collaboration-mcp-server",
8
+ version: "0.1.0"
9
+ });
10
+ const client = createCollaborationClient();
11
+ const jsonContent = (payload) => ({
12
+ content: [
13
+ {
14
+ type: "text",
15
+ text: JSON.stringify(payload, null, 2)
16
+ }
17
+ ]
18
+ });
19
+ server.registerTool("get_collaboration_context", {
20
+ title: "Get Collaboration Context",
21
+ description: "Read project, task, knowledge, plan, path claim, and submission context.",
22
+ inputSchema: {
23
+ projectSlug: z.string().optional().describe("Project slug. Defaults to server configuration."),
24
+ taskId: z.string().optional().describe("Task id. Defaults to the latest active task.")
25
+ }
26
+ }, async (input) => jsonContent(await client.getCollaborationContext(input)));
27
+ server.registerTool("get_project_conflicts", {
28
+ title: "Get Project Conflicts",
29
+ description: "Read active path claims and computed overlap conflicts for a project.",
30
+ inputSchema: {
31
+ projectSlug: z.string().optional().describe("Project slug. Defaults to server configuration.")
32
+ }
33
+ }, async (input) => jsonContent(await client.getProjectConflicts(input)));
34
+ server.registerTool("register_session", {
35
+ title: "Register Session",
36
+ description: "Register the current agent session for a project token.",
37
+ inputSchema: {
38
+ taskId: z.string().optional().describe("Optional task id. Defaults to the latest active task."),
39
+ client: z.enum(["codex-app", "codex-cli", "ide-agent"])
40
+ }
41
+ }, async (input) => jsonContent(await client.registerSession(input)));
42
+ server.registerTool("heartbeat", {
43
+ title: "Heartbeat",
44
+ description: "Update lastSeenAt and status for a registered agent session.",
45
+ inputSchema: {
46
+ agentSessionId: z.string().describe("Agent session id returned by register_session."),
47
+ status: z.enum(["online", "idle", "offline"]).optional()
48
+ }
49
+ }, async (input) => jsonContent(await client.heartbeat(input)));
50
+ server.registerTool("claim_paths", {
51
+ title: "Claim Paths",
52
+ description: "Declare files, directories, modules, or services an agent is actively editing.",
53
+ inputSchema: {
54
+ taskId: z.string().describe("Task id for the current work."),
55
+ agentSessionId: z.string().optional().describe("Agent session id. Defaults to auto-registered session."),
56
+ ownerMemberId: z.string().optional().describe("Member id. Defaults to the project token owner."),
57
+ claims: z
58
+ .array(z.object({
59
+ path: z.string().describe("Repository path or logical module/service name."),
60
+ claimType: z.enum(["file", "directory", "module", "service"])
61
+ }))
62
+ .min(1)
63
+ }
64
+ }, async (input) => jsonContent(await client.claimPaths(input)));
65
+ server.registerTool("report_progress", {
66
+ title: "Report Progress",
67
+ description: "Report started, progress, finding, submitted, or blocked status for a task.",
68
+ inputSchema: {
69
+ taskId: z.string().describe("Task id for the current work."),
70
+ agentSessionId: z.string().optional().describe("Agent session id. Defaults to auto-registered session."),
71
+ kind: z.enum(["started", "progress", "finding", "submitted", "blocked"]),
72
+ message: z.string().describe("Short progress update shown on the dashboard.")
73
+ }
74
+ }, async (input) => jsonContent(await client.reportProgress(input)));
75
+ server.registerTool("submit_work", {
76
+ title: "Submit Work",
77
+ description: "Submit a branch, patch, or pull request reference into the merge queue.",
78
+ inputSchema: {
79
+ taskId: z.string().describe("Task id for the completed work."),
80
+ title: z.string().describe("Human-readable submission title."),
81
+ kind: z.enum(["branch", "patch", "pull_request"]),
82
+ reference: z.string().describe("Branch name, patch reference, or pull request URL.")
83
+ }
84
+ }, async (input) => jsonContent(await client.submitWork(input)));
85
+ const main = async () => {
86
+ const transport = new StdioServerTransport();
87
+ await server.connect(transport);
88
+ console.error("Agent Collaboration MCP server running on stdio");
89
+ };
90
+ main().catch((error) => {
91
+ console.error(error);
92
+ process.exit(1);
93
+ });
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@agent-collab/mcp-server",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "bin": {
9
+ "agent-collab-mcp-server": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "build": "tsc -p tsconfig.build.json",
19
+ "dev": "tsx src/index.ts",
20
+ "prepack": "npm run build",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run"
23
+ },
24
+ "dependencies": {
25
+ "@modelcontextprotocol/sdk": "^1.18.0",
26
+ "zod": "^4.4.3"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^24.0.0",
30
+ "tsx": "^4.0.0",
31
+ "typescript": "^5.9.0",
32
+ "vitest": "^4.0.0"
33
+ }
34
+ }