@spexly/mcp-server 1.0.2 → 1.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.
Files changed (2) hide show
  1. package/dist/index.js +65 -44
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -33,30 +33,30 @@ const { apiKey: API_KEY, apiUrl: API_URL } = parseArgs();
33
33
  // Import event types from centralized module
34
34
  import { EVENT_TYPES } from './eventTypes.js';
35
35
  const SPEXLY_DIR = path.join(os.homedir(), '.spexly');
36
- const CONVERSATIONS_FILE = path.join(SPEXLY_DIR, 'conversations.json');
37
- function loadConversations() {
36
+ const AGENTS_FILE = path.join(SPEXLY_DIR, 'agents.json');
37
+ function loadAgentMap() {
38
38
  try {
39
- if (!fs.existsSync(CONVERSATIONS_FILE))
39
+ if (!fs.existsSync(AGENTS_FILE))
40
40
  return new Map();
41
- return new Map(Object.entries(JSON.parse(fs.readFileSync(CONVERSATIONS_FILE, 'utf-8'))));
41
+ return new Map(Object.entries(JSON.parse(fs.readFileSync(AGENTS_FILE, 'utf-8'))));
42
42
  }
43
43
  catch {
44
44
  return new Map();
45
45
  }
46
46
  }
47
- function saveConversation(conversationId, agent) {
47
+ function saveAgent(id, agent) {
48
48
  try {
49
49
  fs.mkdirSync(SPEXLY_DIR, { recursive: true });
50
- const data = loadConversations();
51
- data.set(conversationId, agent);
52
- fs.writeFileSync(CONVERSATIONS_FILE, JSON.stringify(Object.fromEntries(data), null, 2));
50
+ const data = loadAgentMap();
51
+ data.set(id, agent);
52
+ fs.writeFileSync(AGENTS_FILE, JSON.stringify(Object.fromEntries(data), null, 2));
53
53
  }
54
54
  catch (err) {
55
- console.error('[WARN] Failed to save conversation:', err);
55
+ console.error('[WARN] Failed to save agent:', err);
56
56
  }
57
57
  }
58
- function getAgentForConversation(conversationId) {
59
- return loadConversations().get(conversationId);
58
+ function getAgent(id) {
59
+ return loadAgentMap().get(id);
60
60
  }
61
61
  // ============================================
62
62
  // API Client
@@ -86,20 +86,23 @@ async function apiRequest(method, path, body) {
86
86
  // ============================================
87
87
  const server = new McpServer({
88
88
  name: "spexly",
89
- version: "1.0.0"
89
+ version: "1.1.0"
90
90
  });
91
91
  // Tool: register_agent
92
- server.tool("register_agent", "Register yourself as an agent with Spexly for status monitoring. " +
93
- "CALL THIS ONCE at the start of a conversation. " +
94
- "Use the same conversationId for all calls in this conversation - it must be consistent. " +
95
- "This is IDEMPOTENT - safe to call multiple times with the same conversationId.", {
96
- conversationId: z.string().describe("Unique, consistent identifier for this conversation (use the same value for all calls)"),
97
- name: z.string().describe("Descriptive name for this conversation (e.g., 'Implementing MCP Server')"),
98
- workspace: z.string().optional().describe("Optional workspace name for grouping agents (immutable after registration)"),
99
- parentId: z.string().optional().describe("Optional parent agent ID for nested/hierarchical agents")
100
- }, async ({ conversationId, name, workspace, parentId }) => {
92
+ server.registerTool("register_agent", {
93
+ description: "Register yourself as an agent with Spexly for status monitoring. " +
94
+ "CALL THIS ONCE at the start of a conversation. " +
95
+ "Use the same id for all calls in this conversation - it must be consistent. " +
96
+ "This is IDEMPOTENT - safe to call multiple times with the same id.",
97
+ inputSchema: {
98
+ id: z.string().describe("Unique, consistent identifier for this agent (use the same value for all calls)"),
99
+ name: z.string().describe("Descriptive name for this agent (e.g., 'Implementing MCP Server')"),
100
+ workspace: z.string().optional().describe("Optional workspace name for grouping agents (immutable after registration)"),
101
+ parentId: z.string().optional().describe("Optional parent agent id for hierarchical agents")
102
+ }
103
+ }, async ({ id, name, workspace, parentId }) => {
101
104
  // Check if already registered
102
- const existing = getAgentForConversation(conversationId);
105
+ const existing = getAgent(id);
103
106
  if (existing) {
104
107
  return {
105
108
  content: [{
@@ -113,17 +116,29 @@ server.tool("register_agent", "Register yourself as an agent with Spexly for sta
113
116
  }]
114
117
  };
115
118
  }
119
+ // Resolve parentId through mapping if provided
120
+ let resolvedParentId = null;
121
+ if (parentId) {
122
+ const parentAgent = getAgent(parentId);
123
+ if (!parentAgent) {
124
+ return {
125
+ content: [{ type: "text", text: `Error: Parent agent "${parentId}" not found. Register the parent first.` }],
126
+ isError: true
127
+ };
128
+ }
129
+ resolvedParentId = parentAgent.agentId;
130
+ }
116
131
  // Register new agent
117
132
  const result = await apiRequest("POST", "/agents/register", {
118
133
  name,
119
134
  workspace: workspace || null,
120
- parentId: parentId || null
135
+ parentId: resolvedParentId
121
136
  });
122
137
  if (!result.ok) {
123
138
  return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
124
139
  }
125
140
  // Persist the mapping
126
- saveConversation(conversationId, {
141
+ saveAgent(id, {
127
142
  agentId: result.data.id,
128
143
  name: result.data.name,
129
144
  workspace: result.data.workspace || null,
@@ -142,16 +157,19 @@ server.tool("register_agent", "Register yourself as an agent with Spexly for sta
142
157
  };
143
158
  });
144
159
  // Tool: update_agent
145
- server.tool("update_agent", "Update the name of a registered agent. " +
146
- "Use this to rename an agent if the conversation context has changed. " +
147
- "Use the same conversationId as your register_agent call.", {
148
- conversationId: z.string().describe("Unique, consistent identifier for this conversation (use the same value for all calls)"),
149
- name: z.string().describe("New descriptive name for this conversation")
150
- }, async ({ conversationId, name }) => {
151
- const agent = getAgentForConversation(conversationId);
160
+ server.registerTool("update_agent", {
161
+ description: "Update the name of a registered agent. " +
162
+ "Use this to rename an agent if the context has changed. " +
163
+ "Use the same id as your register_agent call.",
164
+ inputSchema: {
165
+ id: z.string().describe("Unique, consistent identifier for this agent (use the same value for all calls)"),
166
+ name: z.string().describe("New descriptive name for this agent")
167
+ }
168
+ }, async ({ id, name }) => {
169
+ const agent = getAgent(id);
152
170
  if (!agent) {
153
171
  return {
154
- content: [{ type: "text", text: "Error: No agent registered for this conversation. Call register_agent first." }],
172
+ content: [{ type: "text", text: "Error: No agent registered with this id. Call register_agent first." }],
155
173
  isError: true
156
174
  };
157
175
  }
@@ -160,7 +178,7 @@ server.tool("update_agent", "Update the name of a registered agent. " +
160
178
  return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
161
179
  }
162
180
  // Update local cache
163
- saveConversation(conversationId, {
181
+ saveAgent(id, {
164
182
  ...agent,
165
183
  name: result.data.name
166
184
  });
@@ -177,16 +195,19 @@ server.tool("update_agent", "Update the name of a registered agent. " +
177
195
  };
178
196
  });
179
197
  // Tool: report_event
180
- server.tool("report_event", "Report your current status to the Spexly monitoring dashboard. " +
181
- "CALL THIS whenever your working state changes: " +
182
- "task_start (beginning work), task_complete (finished), thinking (reasoning NOT working), " +
183
- "error (problem), idle (waiting), blocked (stuck), needs_input (need clarification). " +
184
- "Use the same conversationId as your register_agent call.", {
185
- conversationId: z.string().describe("Unique, consistent identifier for this conversation (use the same value for all calls)"),
186
- eventType: z.enum(EVENT_TYPES).describe("Your current state"),
187
- message: z.string().optional().describe("Brief description of what you're doing")
188
- }, async ({ conversationId, eventType, message }) => {
189
- const agent = getAgentForConversation(conversationId);
198
+ server.registerTool("report_event", {
199
+ description: "Report your current status to the Spexly monitoring dashboard. " +
200
+ "CALL THIS whenever your working state changes: " +
201
+ "task_start (beginning work), task_complete (finished), thinking (reasoning NOT working), " +
202
+ "error (problem), idle (waiting), blocked (stuck), needs_input (need clarification). " +
203
+ "Use the same id as your register_agent call.",
204
+ inputSchema: {
205
+ id: z.string().describe("Unique, consistent identifier for this agent (use the same value for all calls)"),
206
+ eventType: z.enum(EVENT_TYPES).describe("Your current state"),
207
+ message: z.string().optional().describe("Brief description of what you're doing")
208
+ }
209
+ }, async ({ id, eventType, message }) => {
210
+ const agent = getAgent(id);
190
211
  if (!agent) {
191
212
  return {
192
213
  content: [{ type: "text", text: "Error: Not registered. Call register_agent first." }],
@@ -216,7 +237,7 @@ async function main() {
216
237
  const transport = new StdioServerTransport();
217
238
  await server.connect(transport);
218
239
  console.error("Spexly MCP server running");
219
- console.error(`Conversations: ${CONVERSATIONS_FILE}`);
240
+ console.error(`Agents file: ${AGENTS_FILE}`);
220
241
  }
221
242
  main().catch((err) => {
222
243
  console.error("Fatal error:", err);
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@spexly/mcp-server",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "Spexly MCP Server - AI agent status monitoring via Model Context Protocol",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
- "spexly-mcp": "./dist/index.js"
7
+ "spexly-mcp": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist",
@@ -44,4 +44,4 @@
44
44
  "engines": {
45
45
  "node": ">=18.0.0"
46
46
  }
47
- }
47
+ }