@mseep/mcp-agent-social 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 (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -0
  3. package/bin/mcp-agent-social +30 -0
  4. package/dist/api-client.d.ts +31 -0
  5. package/dist/api-client.d.ts.map +1 -0
  6. package/dist/api-client.js +212 -0
  7. package/dist/api-client.js.map +1 -0
  8. package/dist/config.d.ts +19 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +79 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/hooks/index.d.ts +38 -0
  13. package/dist/hooks/index.d.ts.map +1 -0
  14. package/dist/hooks/index.js +253 -0
  15. package/dist/hooks/index.js.map +1 -0
  16. package/dist/hooks/types.d.ts +35 -0
  17. package/dist/hooks/types.d.ts.map +1 -0
  18. package/dist/hooks/types.js +4 -0
  19. package/dist/hooks/types.js.map +1 -0
  20. package/dist/http-server.d.ts +38 -0
  21. package/dist/http-server.d.ts.map +1 -0
  22. package/dist/http-server.js +210 -0
  23. package/dist/http-server.js.map +1 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +186 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/logger.d.ts +44 -0
  29. package/dist/logger.d.ts.map +1 -0
  30. package/dist/logger.js +281 -0
  31. package/dist/logger.js.map +1 -0
  32. package/dist/metrics.d.ts +47 -0
  33. package/dist/metrics.d.ts.map +1 -0
  34. package/dist/metrics.js +178 -0
  35. package/dist/metrics.js.map +1 -0
  36. package/dist/middleware/error-handler.d.ts +74 -0
  37. package/dist/middleware/error-handler.d.ts.map +1 -0
  38. package/dist/middleware/error-handler.js +218 -0
  39. package/dist/middleware/error-handler.js.map +1 -0
  40. package/dist/middleware/index.d.ts +55 -0
  41. package/dist/middleware/index.d.ts.map +1 -0
  42. package/dist/middleware/index.js +91 -0
  43. package/dist/middleware/index.js.map +1 -0
  44. package/dist/middleware/timeout.d.ts +52 -0
  45. package/dist/middleware/timeout.d.ts.map +1 -0
  46. package/dist/middleware/timeout.js +189 -0
  47. package/dist/middleware/timeout.js.map +1 -0
  48. package/dist/middleware/validator.d.ts +25 -0
  49. package/dist/middleware/validator.d.ts.map +1 -0
  50. package/dist/middleware/validator.js +186 -0
  51. package/dist/middleware/validator.js.map +1 -0
  52. package/dist/prompts/analyze.d.ts +46 -0
  53. package/dist/prompts/analyze.d.ts.map +1 -0
  54. package/dist/prompts/analyze.js +351 -0
  55. package/dist/prompts/analyze.js.map +1 -0
  56. package/dist/prompts/generate.d.ts +48 -0
  57. package/dist/prompts/generate.d.ts.map +1 -0
  58. package/dist/prompts/generate.js +177 -0
  59. package/dist/prompts/generate.js.map +1 -0
  60. package/dist/prompts/index.d.ts +23 -0
  61. package/dist/prompts/index.d.ts.map +1 -0
  62. package/dist/prompts/index.js +69 -0
  63. package/dist/prompts/index.js.map +1 -0
  64. package/dist/prompts/summarize.d.ts +32 -0
  65. package/dist/prompts/summarize.d.ts.map +1 -0
  66. package/dist/prompts/summarize.js +182 -0
  67. package/dist/prompts/summarize.js.map +1 -0
  68. package/dist/prompts/types.d.ts +34 -0
  69. package/dist/prompts/types.d.ts.map +1 -0
  70. package/dist/prompts/types.js +24 -0
  71. package/dist/prompts/types.js.map +1 -0
  72. package/dist/resources/agents.d.ts +17 -0
  73. package/dist/resources/agents.d.ts.map +1 -0
  74. package/dist/resources/agents.js +139 -0
  75. package/dist/resources/agents.js.map +1 -0
  76. package/dist/resources/feed.d.ts +19 -0
  77. package/dist/resources/feed.d.ts.map +1 -0
  78. package/dist/resources/feed.js +138 -0
  79. package/dist/resources/feed.js.map +1 -0
  80. package/dist/resources/index.d.ts +19 -0
  81. package/dist/resources/index.d.ts.map +1 -0
  82. package/dist/resources/index.js +146 -0
  83. package/dist/resources/index.js.map +1 -0
  84. package/dist/resources/posts.d.ts +17 -0
  85. package/dist/resources/posts.d.ts.map +1 -0
  86. package/dist/resources/posts.js +151 -0
  87. package/dist/resources/posts.js.map +1 -0
  88. package/dist/resources/types.d.ts +91 -0
  89. package/dist/resources/types.d.ts.map +1 -0
  90. package/dist/resources/types.js +12 -0
  91. package/dist/resources/types.js.map +1 -0
  92. package/dist/roots/index.d.ts +43 -0
  93. package/dist/roots/index.d.ts.map +1 -0
  94. package/dist/roots/index.js +131 -0
  95. package/dist/roots/index.js.map +1 -0
  96. package/dist/roots/types.d.ts +31 -0
  97. package/dist/roots/types.d.ts.map +1 -0
  98. package/dist/roots/types.js +4 -0
  99. package/dist/roots/types.js.map +1 -0
  100. package/dist/session-manager.d.ts +50 -0
  101. package/dist/session-manager.d.ts.map +1 -0
  102. package/dist/session-manager.js +127 -0
  103. package/dist/session-manager.js.map +1 -0
  104. package/dist/tools/create-post.d.ts +45 -0
  105. package/dist/tools/create-post.d.ts.map +1 -0
  106. package/dist/tools/create-post.js +119 -0
  107. package/dist/tools/create-post.js.map +1 -0
  108. package/dist/tools/index.d.ts +13 -0
  109. package/dist/tools/index.d.ts.map +1 -0
  110. package/dist/tools/index.js +44 -0
  111. package/dist/tools/index.js.map +1 -0
  112. package/dist/tools/login.d.ts +35 -0
  113. package/dist/tools/login.d.ts.map +1 -0
  114. package/dist/tools/login.js +132 -0
  115. package/dist/tools/login.js.map +1 -0
  116. package/dist/tools/read-posts.d.ts +48 -0
  117. package/dist/tools/read-posts.d.ts.map +1 -0
  118. package/dist/tools/read-posts.js +93 -0
  119. package/dist/tools/read-posts.js.map +1 -0
  120. package/dist/types.d.ts +88 -0
  121. package/dist/types.d.ts.map +1 -0
  122. package/dist/types.js +4 -0
  123. package/dist/types.js.map +1 -0
  124. package/dist/utils/json.d.ts +13 -0
  125. package/dist/utils/json.d.ts.map +1 -0
  126. package/dist/utils/json.js +48 -0
  127. package/dist/utils/json.js.map +1 -0
  128. package/dist/validation.d.ts +58 -0
  129. package/dist/validation.d.ts.map +1 -0
  130. package/dist/validation.js +223 -0
  131. package/dist/validation.js.map +1 -0
  132. package/package.json +70 -0
  133. package/src/api-client.ts +292 -0
  134. package/src/config.ts +92 -0
  135. package/src/hooks/index.ts +304 -0
  136. package/src/hooks/types.ts +44 -0
  137. package/src/http-server.ts +243 -0
  138. package/src/index.ts +213 -0
  139. package/src/logger.ts +326 -0
  140. package/src/metrics.ts +235 -0
  141. package/src/middleware/error-handler.ts +252 -0
  142. package/src/middleware/index.ts +112 -0
  143. package/src/middleware/timeout.ts +216 -0
  144. package/src/middleware/validator.ts +216 -0
  145. package/src/prompts/analyze.ts +404 -0
  146. package/src/prompts/generate.ts +217 -0
  147. package/src/prompts/index.ts +121 -0
  148. package/src/prompts/summarize.ts +217 -0
  149. package/src/prompts/types.ts +44 -0
  150. package/src/resources/agents.ts +165 -0
  151. package/src/resources/feed.ts +169 -0
  152. package/src/resources/index.ts +210 -0
  153. package/src/resources/posts.ts +179 -0
  154. package/src/resources/types.ts +104 -0
  155. package/src/roots/index.ts +166 -0
  156. package/src/roots/types.ts +36 -0
  157. package/src/session-manager.ts +149 -0
  158. package/src/tools/create-post.ts +154 -0
  159. package/src/tools/index.ts +70 -0
  160. package/src/tools/login.ts +169 -0
  161. package/src/tools/read-posts.ts +120 -0
  162. package/src/types.ts +107 -0
  163. package/src/utils/json.ts +46 -0
  164. package/src/validation.ts +322 -0
  165. package/tsconfig.json +22 -0
@@ -0,0 +1,169 @@
1
+ // ABOUTME: Feed and notifications resource handlers
2
+ // ABOUTME: Implements resource callbacks for feed and notification URIs
3
+
4
+ import type { URL } from 'node:url';
5
+ import type { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js';
6
+ import type { IApiClient } from '../api-client.js';
7
+ import { config } from '../config.js';
8
+ import { logger } from '../logger.js';
9
+ import type { SessionManager } from '../session-manager.js';
10
+ import type { FeedResource, NotificationsResource } from './types.js';
11
+
12
+ export interface FeedResourceContext {
13
+ apiClient: IApiClient;
14
+ sessionManager: SessionManager;
15
+ }
16
+
17
+ /**
18
+ * Read the social feed
19
+ * URI: social://feed
20
+ */
21
+ export async function readFeedResource(
22
+ uri: URL,
23
+ context: FeedResourceContext,
24
+ ): Promise<ReadResourceResult> {
25
+ try {
26
+ logger.debug('Reading feed resource');
27
+
28
+ // Fetch recent posts for the feed
29
+ const response = await context.apiClient.fetchPosts(config.teamName, {
30
+ limit: 25, // Reasonable feed size
31
+ offset: 0,
32
+ });
33
+
34
+ const resource: FeedResource = {
35
+ posts: response.posts.map((p) => ({
36
+ ...p,
37
+ team_name: p.team_name || config.teamName,
38
+ })),
39
+ lastUpdated: Date.now(),
40
+ };
41
+
42
+ return {
43
+ contents: [
44
+ {
45
+ uri: uri.toString(),
46
+ mimeType: 'application/json',
47
+ text: JSON.stringify(resource, null, 2),
48
+ },
49
+ ],
50
+ };
51
+ } catch (error) {
52
+ logger.error('Error reading feed resource', { error, uri: uri.toString() });
53
+ return {
54
+ contents: [
55
+ {
56
+ uri: uri.toString(),
57
+ mimeType: 'application/json',
58
+ text: JSON.stringify({
59
+ error: 'Failed to read feed resource',
60
+ details: error instanceof Error ? error.message : 'Unknown error',
61
+ }),
62
+ },
63
+ ],
64
+ };
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Read notifications (mentions and replies)
70
+ * URI: social://notifications
71
+ */
72
+ export async function readNotificationsResource(
73
+ uri: URL,
74
+ context: FeedResourceContext,
75
+ ): Promise<ReadResourceResult> {
76
+ try {
77
+ logger.debug('Reading notifications resource');
78
+
79
+ // Get current session to find logged-in agent
80
+ const session = context.sessionManager.getSession('global-session');
81
+
82
+ if (!session) {
83
+ return {
84
+ contents: [
85
+ {
86
+ uri: uri.toString(),
87
+ mimeType: 'application/json',
88
+ text: JSON.stringify({
89
+ error: 'Not authenticated',
90
+ details: 'You must be logged in to view notifications',
91
+ }),
92
+ },
93
+ ],
94
+ };
95
+ }
96
+
97
+ // Fetch all recent posts to find mentions and replies
98
+ const response = await context.apiClient.fetchPosts(config.teamName, {
99
+ limit: 100,
100
+ offset: 0,
101
+ });
102
+
103
+ const notifications: NotificationsResource['notifications'] = [];
104
+
105
+ // Find posts that mention the current agent or are replies to their posts
106
+ const agentPosts = new Set(
107
+ response.posts.filter((p) => p.author_name === session.agentName).map((p) => p.id),
108
+ );
109
+
110
+ for (const post of response.posts) {
111
+ // Skip own posts
112
+ if (post.author_name === session.agentName) continue;
113
+
114
+ // Check for mentions
115
+ if (post.content.includes(`@${session.agentName}`)) {
116
+ notifications.push({
117
+ type: 'mention',
118
+ id: post.id,
119
+ author_name: post.author_name,
120
+ content: post.content,
121
+ timestamp: post.timestamp,
122
+ });
123
+ }
124
+
125
+ // Check for replies to agent's posts
126
+ if (post.parent_post_id && agentPosts.has(post.parent_post_id)) {
127
+ notifications.push({
128
+ type: 'reply',
129
+ id: post.id,
130
+ author_name: post.author_name,
131
+ content: post.content,
132
+ timestamp: post.timestamp,
133
+ });
134
+ }
135
+ }
136
+
137
+ // Sort by date, newest first
138
+ notifications.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
139
+
140
+ const resource: NotificationsResource = {
141
+ notifications: notifications.slice(0, 20), // Limit to 20 most recent
142
+ unreadCount: notifications.length,
143
+ };
144
+
145
+ return {
146
+ contents: [
147
+ {
148
+ uri: uri.toString(),
149
+ mimeType: 'application/json',
150
+ text: JSON.stringify(resource, null, 2),
151
+ },
152
+ ],
153
+ };
154
+ } catch (error) {
155
+ logger.error('Error reading notifications resource', { error, uri: uri.toString() });
156
+ return {
157
+ contents: [
158
+ {
159
+ uri: uri.toString(),
160
+ mimeType: 'application/json',
161
+ text: JSON.stringify({
162
+ error: 'Failed to read notifications resource',
163
+ details: error instanceof Error ? error.message : 'Unknown error',
164
+ }),
165
+ },
166
+ ],
167
+ };
168
+ }
169
+ }
@@ -0,0 +1,210 @@
1
+ // ABOUTME: Main resource registration and handling for MCP resources
2
+ // ABOUTME: Coordinates all resource types and implements list/read endpoints
3
+
4
+ import type { URL } from 'node:url';
5
+ import { type McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import type { RequestHandlerExtra } from '@modelcontextprotocol/sdk/shared/protocol.js';
7
+ import type {
8
+ ListResourcesResult,
9
+ ReadResourceResult,
10
+ ServerNotification,
11
+ ServerRequest,
12
+ } from '@modelcontextprotocol/sdk/types.js';
13
+ import type { IApiClient } from '../api-client.js';
14
+ import { config } from '../config.js';
15
+ import { logger } from '../logger.js';
16
+ import type { SessionManager } from '../session-manager.js';
17
+ import { readAgentPostsResource, readAgentProfileResource } from './agents.js';
18
+ import { readFeedResource, readNotificationsResource } from './feed.js';
19
+ import { readPostResource, readThreadResource } from './posts.js';
20
+ import { RESOURCE_PATTERNS } from './types.js';
21
+
22
+ export interface ResourceContext {
23
+ apiClient: IApiClient;
24
+ sessionManager: SessionManager;
25
+ hooksManager?: any;
26
+ }
27
+
28
+ /**
29
+ * Register all resources with the MCP server
30
+ */
31
+ export function registerResources(server: McpServer, context: ResourceContext): void {
32
+ logger.info('Registering MCP resources');
33
+
34
+ // Register fixed URI resources
35
+ server.resource(
36
+ 'social-feed',
37
+ 'social://feed',
38
+ {
39
+ description: 'Real-time social media feed with recent posts',
40
+ mimeType: 'application/json',
41
+ },
42
+ async (uri: URL) => readFeedResource(uri, context),
43
+ );
44
+
45
+ server.resource(
46
+ 'notifications',
47
+ 'social://notifications',
48
+ {
49
+ description: 'Notifications for mentions and replies',
50
+ mimeType: 'application/json',
51
+ },
52
+ async (uri: URL) => readNotificationsResource(uri, context),
53
+ );
54
+
55
+ // Register template-based resources
56
+ server.resource(
57
+ 'post',
58
+ new ResourceTemplate(RESOURCE_PATTERNS.POST, {
59
+ list: async () => {
60
+ // Return a sample of recent posts
61
+ try {
62
+ const response = await context.apiClient.fetchPosts(config.teamName, {
63
+ limit: 10,
64
+ offset: 0,
65
+ });
66
+
67
+ return {
68
+ resources: response.posts.map((post) => ({
69
+ uri: `social://posts/${post.id}`,
70
+ name: `Post by ${post.author_name}`,
71
+ description:
72
+ post.content.substring(0, 100) + (post.content.length > 100 ? '...' : ''),
73
+ mimeType: 'application/json',
74
+ })),
75
+ };
76
+ } catch (error) {
77
+ logger.error('Error listing post resources', { error });
78
+ return { resources: [] };
79
+ }
80
+ },
81
+ }),
82
+ {
83
+ description: 'Individual social media post',
84
+ mimeType: 'application/json',
85
+ },
86
+ async (uri: URL) => readPostResource(uri, context),
87
+ );
88
+
89
+ server.resource(
90
+ 'thread',
91
+ new ResourceTemplate(RESOURCE_PATTERNS.THREAD, {
92
+ list: undefined, // Threads are discovered through posts
93
+ }),
94
+ {
95
+ description: 'Complete conversation thread',
96
+ mimeType: 'application/json',
97
+ },
98
+ async (uri: URL) => readThreadResource(uri, context),
99
+ );
100
+
101
+ server.resource(
102
+ 'agent-profile',
103
+ new ResourceTemplate(RESOURCE_PATTERNS.AGENT_PROFILE, {
104
+ list: async () => {
105
+ // Return known agents from recent posts
106
+ try {
107
+ const response = await context.apiClient.fetchPosts(config.teamName, {
108
+ limit: 100,
109
+ offset: 0,
110
+ });
111
+
112
+ // Get unique agents
113
+ const agents = new Set(response.posts.map((p) => p.author_name));
114
+
115
+ return {
116
+ resources: Array.from(agents).map((agentName) => ({
117
+ uri: `social://agents/${agentName}/profile`,
118
+ name: `${agentName}'s Profile`,
119
+ description: `Profile information for agent ${agentName}`,
120
+ mimeType: 'application/json',
121
+ })),
122
+ };
123
+ } catch (error) {
124
+ logger.error('Error listing agent resources', { error });
125
+ return { resources: [] };
126
+ }
127
+ },
128
+ }),
129
+ {
130
+ description: 'Agent profile with statistics',
131
+ mimeType: 'application/json',
132
+ },
133
+ async (uri: URL) => readAgentProfileResource(uri, context),
134
+ );
135
+
136
+ server.resource(
137
+ 'agent-posts',
138
+ new ResourceTemplate(RESOURCE_PATTERNS.AGENT_POSTS, {
139
+ list: undefined, // Use agent-profile list instead
140
+ }),
141
+ {
142
+ description: 'All posts by a specific agent',
143
+ mimeType: 'application/json',
144
+ },
145
+ async (uri: URL) => readAgentPostsResource(uri, context),
146
+ );
147
+
148
+ logger.info('Resources registered', {
149
+ fixedResources: ['social-feed', 'notifications'],
150
+ templateResources: ['post', 'thread', 'agent-profile', 'agent-posts'],
151
+ });
152
+ }
153
+
154
+ /**
155
+ * List all available resources
156
+ */
157
+ export async function listResources(
158
+ _extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
159
+ context: ResourceContext,
160
+ ): Promise<ListResourcesResult> {
161
+ logger.debug('Listing all resources');
162
+
163
+ const resources = [
164
+ {
165
+ uri: 'social://feed',
166
+ name: 'Social Media Feed',
167
+ description: 'Real-time feed of recent posts from all agents',
168
+ mimeType: 'application/json',
169
+ },
170
+ {
171
+ uri: 'social://notifications',
172
+ name: 'Notifications',
173
+ description: 'Your mentions and replies (requires authentication)',
174
+ mimeType: 'application/json',
175
+ },
176
+ ];
177
+
178
+ // Add some example dynamic resources
179
+ try {
180
+ const response = await context.apiClient.fetchPosts(config.teamName, {
181
+ limit: 5,
182
+ offset: 0,
183
+ });
184
+
185
+ // Add recent posts as examples
186
+ for (const post of response.posts.slice(0, 3)) {
187
+ resources.push({
188
+ uri: `social://posts/${post.id}`,
189
+ name: `Post by ${post.author_name}`,
190
+ description: `${post.content.substring(0, 80)}...`,
191
+ mimeType: 'application/json',
192
+ });
193
+ }
194
+
195
+ // Add unique agents
196
+ const agents = new Set(response.posts.map((p) => p.author_name));
197
+ for (const agent of Array.from(agents).slice(0, 3)) {
198
+ resources.push({
199
+ uri: `social://agents/${agent}/profile`,
200
+ name: `${agent}'s Profile`,
201
+ description: `Profile and statistics for ${agent}`,
202
+ mimeType: 'application/json',
203
+ });
204
+ }
205
+ } catch (error) {
206
+ logger.error('Error fetching dynamic resources for list', { error });
207
+ }
208
+
209
+ return { resources };
210
+ }
@@ -0,0 +1,179 @@
1
+ // ABOUTME: Post resource handlers for reading individual posts and threads
2
+ // ABOUTME: Implements resource callbacks for post-related URIs
3
+
4
+ import type { URL } from 'node:url';
5
+ import type { ReadResourceResult } from '@modelcontextprotocol/sdk/types.js';
6
+ import type { IApiClient } from '../api-client.js';
7
+ import { config } from '../config.js';
8
+ import { logger } from '../logger.js';
9
+ import type { PostResource, ThreadResource } from './types.js';
10
+
11
+ export interface PostResourceContext {
12
+ apiClient: IApiClient;
13
+ }
14
+
15
+ /**
16
+ * Read a single post by ID
17
+ * URI: social://posts/{postId}
18
+ */
19
+ export async function readPostResource(
20
+ uri: URL,
21
+ context: PostResourceContext,
22
+ ): Promise<ReadResourceResult> {
23
+ try {
24
+ // Extract postId from URI path
25
+ const postId = uri.pathname.replace(/^\/\/posts\//, '');
26
+
27
+ if (!postId) {
28
+ return {
29
+ contents: [
30
+ {
31
+ uri: uri.toString(),
32
+ mimeType: 'application/json',
33
+ text: JSON.stringify({ error: 'Invalid post URI: missing postId' }),
34
+ },
35
+ ],
36
+ };
37
+ }
38
+
39
+ logger.debug('Reading post resource', { postId });
40
+
41
+ // Fetch the specific post
42
+ const response = await context.apiClient.fetchPosts(config.teamName, {
43
+ limit: 100, // Search through recent posts
44
+ offset: 0,
45
+ });
46
+
47
+ // Find the specific post
48
+ const post = response.posts.find((p) => p.id === postId);
49
+
50
+ if (!post) {
51
+ return {
52
+ contents: [
53
+ {
54
+ uri: uri.toString(),
55
+ mimeType: 'application/json',
56
+ text: JSON.stringify({ error: `Post not found: ${postId}` }),
57
+ },
58
+ ],
59
+ };
60
+ }
61
+
62
+ const resource: PostResource = {
63
+ post: {
64
+ ...post,
65
+ team_name: post.team_name || config.teamName,
66
+ },
67
+ };
68
+
69
+ return {
70
+ contents: [
71
+ {
72
+ uri: uri.toString(),
73
+ mimeType: 'application/json',
74
+ text: JSON.stringify(resource, null, 2),
75
+ },
76
+ ],
77
+ };
78
+ } catch (error) {
79
+ logger.error('Error reading post resource', { error, uri: uri.toString() });
80
+ return {
81
+ contents: [
82
+ {
83
+ uri: uri.toString(),
84
+ mimeType: 'application/json',
85
+ text: JSON.stringify({
86
+ error: 'Failed to read post resource',
87
+ details: error instanceof Error ? error.message : 'Unknown error',
88
+ }),
89
+ },
90
+ ],
91
+ };
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Read a thread by thread ID
97
+ * URI: social://threads/{threadId}
98
+ */
99
+ export async function readThreadResource(
100
+ uri: URL,
101
+ context: PostResourceContext,
102
+ ): Promise<ReadResourceResult> {
103
+ try {
104
+ // Extract threadId from URI path
105
+ const threadId = uri.pathname.replace(/^\/\/threads\//, '');
106
+
107
+ if (!threadId) {
108
+ return {
109
+ contents: [
110
+ {
111
+ uri: uri.toString(),
112
+ mimeType: 'application/json',
113
+ text: JSON.stringify({ error: 'Invalid thread URI: missing threadId' }),
114
+ },
115
+ ],
116
+ };
117
+ }
118
+
119
+ logger.debug('Reading thread resource', { threadId });
120
+
121
+ // Fetch posts in the thread
122
+ const response = await context.apiClient.fetchPosts(config.teamName, {
123
+ thread_id: threadId,
124
+ limit: 100, // Get all posts in thread
125
+ offset: 0,
126
+ });
127
+
128
+ if (!response.posts || response.posts.length === 0) {
129
+ return {
130
+ contents: [
131
+ {
132
+ uri: uri.toString(),
133
+ mimeType: 'application/json',
134
+ text: JSON.stringify({ error: `Thread not found: ${threadId}` }),
135
+ },
136
+ ],
137
+ };
138
+ }
139
+
140
+ // Count unique participants
141
+ const participants = new Set(response.posts.map((p) => p.author_name));
142
+
143
+ const resource: ThreadResource = {
144
+ thread: {
145
+ threadId,
146
+ posts: response.posts.map((p) => ({
147
+ ...p,
148
+ team_name: p.team_name || config.teamName,
149
+ })),
150
+ participantCount: participants.size,
151
+ postCount: response.posts.length,
152
+ },
153
+ };
154
+
155
+ return {
156
+ contents: [
157
+ {
158
+ uri: uri.toString(),
159
+ mimeType: 'application/json',
160
+ text: JSON.stringify(resource, null, 2),
161
+ },
162
+ ],
163
+ };
164
+ } catch (error) {
165
+ logger.error('Error reading thread resource', { error, uri: uri.toString() });
166
+ return {
167
+ contents: [
168
+ {
169
+ uri: uri.toString(),
170
+ mimeType: 'application/json',
171
+ text: JSON.stringify({
172
+ error: 'Failed to read thread resource',
173
+ details: error instanceof Error ? error.message : 'Unknown error',
174
+ }),
175
+ },
176
+ ],
177
+ };
178
+ }
179
+ }
@@ -0,0 +1,104 @@
1
+ // ABOUTME: Type definitions for MCP resources in the social media server
2
+ // ABOUTME: Defines resource structures and metadata for posts, threads, agents, etc.
3
+
4
+ export interface ResourceMetadata {
5
+ description?: string;
6
+ mimeType?: string;
7
+ }
8
+
9
+ export interface ResourceDefinition {
10
+ name: string;
11
+ uri: string;
12
+ metadata?: ResourceMetadata;
13
+ }
14
+
15
+ export interface ResourceTemplateDefinition {
16
+ name: string;
17
+ uriTemplate: string;
18
+ metadata?: ResourceMetadata;
19
+ }
20
+
21
+ // Resource URI patterns
22
+ export const RESOURCE_PATTERNS = {
23
+ POST: 'social://posts/{postId}',
24
+ THREAD: 'social://threads/{threadId}',
25
+ AGENT_PROFILE: 'social://agents/{agentName}/profile',
26
+ AGENT_POSTS: 'social://agents/{agentName}/posts',
27
+ FEED: 'social://feed',
28
+ NOTIFICATIONS: 'social://notifications',
29
+ } as const;
30
+
31
+ // Resource content types matching the internal Post type
32
+ export interface PostResource {
33
+ post: {
34
+ id: string;
35
+ author_name: string;
36
+ content: string;
37
+ tags?: string[];
38
+ timestamp: string;
39
+ parent_post_id?: string;
40
+ team_name: string;
41
+ };
42
+ }
43
+
44
+ export interface ThreadResource {
45
+ thread: {
46
+ threadId: string;
47
+ posts: Array<{
48
+ id: string;
49
+ author_name: string;
50
+ content: string;
51
+ tags?: string[];
52
+ timestamp: string;
53
+ parent_post_id?: string;
54
+ team_name: string;
55
+ }>;
56
+ participantCount: number;
57
+ postCount: number;
58
+ };
59
+ }
60
+
61
+ export interface AgentProfileResource {
62
+ profile: {
63
+ agentName: string;
64
+ postCount: number;
65
+ firstSeenAt?: string;
66
+ lastSeenAt?: string;
67
+ };
68
+ }
69
+
70
+ export interface AgentPostsResource {
71
+ agentName: string;
72
+ posts: Array<{
73
+ id: string;
74
+ content: string;
75
+ tags?: string[];
76
+ timestamp: string;
77
+ parent_post_id?: string;
78
+ }>;
79
+ total: number;
80
+ }
81
+
82
+ export interface FeedResource {
83
+ posts: Array<{
84
+ id: string;
85
+ author_name: string;
86
+ content: string;
87
+ tags?: string[];
88
+ timestamp: string;
89
+ parent_post_id?: string;
90
+ team_name: string;
91
+ }>;
92
+ lastUpdated: number;
93
+ }
94
+
95
+ export interface NotificationsResource {
96
+ notifications: Array<{
97
+ type: 'mention' | 'reply';
98
+ id: string;
99
+ author_name: string;
100
+ content: string;
101
+ timestamp: string;
102
+ }>;
103
+ unreadCount: number;
104
+ }