@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.
- package/LICENSE +21 -0
- package/README.md +154 -0
- package/bin/mcp-agent-social +30 -0
- package/dist/api-client.d.ts +31 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +212 -0
- package/dist/api-client.js.map +1 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/hooks/index.d.ts +38 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +253 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/types.d.ts +35 -0
- package/dist/hooks/types.d.ts.map +1 -0
- package/dist/hooks/types.js +4 -0
- package/dist/hooks/types.js.map +1 -0
- package/dist/http-server.d.ts +38 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +210 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +186 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +44 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +281 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +47 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +178 -0
- package/dist/metrics.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +74 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +218 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/index.d.ts +55 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +91 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/timeout.d.ts +52 -0
- package/dist/middleware/timeout.d.ts.map +1 -0
- package/dist/middleware/timeout.js +189 -0
- package/dist/middleware/timeout.js.map +1 -0
- package/dist/middleware/validator.d.ts +25 -0
- package/dist/middleware/validator.d.ts.map +1 -0
- package/dist/middleware/validator.js +186 -0
- package/dist/middleware/validator.js.map +1 -0
- package/dist/prompts/analyze.d.ts +46 -0
- package/dist/prompts/analyze.d.ts.map +1 -0
- package/dist/prompts/analyze.js +351 -0
- package/dist/prompts/analyze.js.map +1 -0
- package/dist/prompts/generate.d.ts +48 -0
- package/dist/prompts/generate.d.ts.map +1 -0
- package/dist/prompts/generate.js +177 -0
- package/dist/prompts/generate.js.map +1 -0
- package/dist/prompts/index.d.ts +23 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +69 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/summarize.d.ts +32 -0
- package/dist/prompts/summarize.d.ts.map +1 -0
- package/dist/prompts/summarize.js +182 -0
- package/dist/prompts/summarize.js.map +1 -0
- package/dist/prompts/types.d.ts +34 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +24 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/resources/agents.d.ts +17 -0
- package/dist/resources/agents.d.ts.map +1 -0
- package/dist/resources/agents.js +139 -0
- package/dist/resources/agents.js.map +1 -0
- package/dist/resources/feed.d.ts +19 -0
- package/dist/resources/feed.d.ts.map +1 -0
- package/dist/resources/feed.js +138 -0
- package/dist/resources/feed.js.map +1 -0
- package/dist/resources/index.d.ts +19 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +146 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/resources/posts.d.ts +17 -0
- package/dist/resources/posts.d.ts.map +1 -0
- package/dist/resources/posts.js +151 -0
- package/dist/resources/posts.js.map +1 -0
- package/dist/resources/types.d.ts +91 -0
- package/dist/resources/types.d.ts.map +1 -0
- package/dist/resources/types.js +12 -0
- package/dist/resources/types.js.map +1 -0
- package/dist/roots/index.d.ts +43 -0
- package/dist/roots/index.d.ts.map +1 -0
- package/dist/roots/index.js +131 -0
- package/dist/roots/index.js.map +1 -0
- package/dist/roots/types.d.ts +31 -0
- package/dist/roots/types.d.ts.map +1 -0
- package/dist/roots/types.js +4 -0
- package/dist/roots/types.js.map +1 -0
- package/dist/session-manager.d.ts +50 -0
- package/dist/session-manager.d.ts.map +1 -0
- package/dist/session-manager.js +127 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/tools/create-post.d.ts +45 -0
- package/dist/tools/create-post.d.ts.map +1 -0
- package/dist/tools/create-post.js +119 -0
- package/dist/tools/create-post.js.map +1 -0
- package/dist/tools/index.d.ts +13 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +44 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/login.d.ts +35 -0
- package/dist/tools/login.d.ts.map +1 -0
- package/dist/tools/login.js +132 -0
- package/dist/tools/login.js.map +1 -0
- package/dist/tools/read-posts.d.ts +48 -0
- package/dist/tools/read-posts.d.ts.map +1 -0
- package/dist/tools/read-posts.js +93 -0
- package/dist/tools/read-posts.js.map +1 -0
- package/dist/types.d.ts +88 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/json.d.ts +13 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +48 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/validation.d.ts +58 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +223 -0
- package/dist/validation.js.map +1 -0
- package/package.json +70 -0
- package/src/api-client.ts +292 -0
- package/src/config.ts +92 -0
- package/src/hooks/index.ts +304 -0
- package/src/hooks/types.ts +44 -0
- package/src/http-server.ts +243 -0
- package/src/index.ts +213 -0
- package/src/logger.ts +326 -0
- package/src/metrics.ts +235 -0
- package/src/middleware/error-handler.ts +252 -0
- package/src/middleware/index.ts +112 -0
- package/src/middleware/timeout.ts +216 -0
- package/src/middleware/validator.ts +216 -0
- package/src/prompts/analyze.ts +404 -0
- package/src/prompts/generate.ts +217 -0
- package/src/prompts/index.ts +121 -0
- package/src/prompts/summarize.ts +217 -0
- package/src/prompts/types.ts +44 -0
- package/src/resources/agents.ts +165 -0
- package/src/resources/feed.ts +169 -0
- package/src/resources/index.ts +210 -0
- package/src/resources/posts.ts +179 -0
- package/src/resources/types.ts +104 -0
- package/src/roots/index.ts +166 -0
- package/src/roots/types.ts +36 -0
- package/src/session-manager.ts +149 -0
- package/src/tools/create-post.ts +154 -0
- package/src/tools/index.ts +70 -0
- package/src/tools/login.ts +169 -0
- package/src/tools/read-posts.ts +120 -0
- package/src/types.ts +107 -0
- package/src/utils/json.ts +46 -0
- package/src/validation.ts +322 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// ABOUTME: MCP Roots implementation for workspace boundaries and operational limits
|
|
2
|
+
// ABOUTME: Defines and enforces multi-tenant configuration and access controls
|
|
3
|
+
|
|
4
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
import { logger } from '../logger.js';
|
|
6
|
+
import { type RootDefinition, RootLimits } from './types.js';
|
|
7
|
+
|
|
8
|
+
interface RootsContext {
|
|
9
|
+
apiClient: any;
|
|
10
|
+
sessionManager: any;
|
|
11
|
+
hooksManager?: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class RootsManager {
|
|
15
|
+
private roots: Map<string, RootDefinition> = new Map();
|
|
16
|
+
private sessionRootMap: Map<string, string> = new Map();
|
|
17
|
+
|
|
18
|
+
constructor() {
|
|
19
|
+
// Define default root for social media workspace
|
|
20
|
+
const defaultRoot: RootDefinition = {
|
|
21
|
+
uri: 'social://workspace',
|
|
22
|
+
name: 'Social Media Workspace',
|
|
23
|
+
description: 'Default workspace for social media operations',
|
|
24
|
+
limits: {
|
|
25
|
+
maxPostsPerHour: 10,
|
|
26
|
+
maxReadRequestsPerMinute: 30,
|
|
27
|
+
maxConcurrentSessions: 5,
|
|
28
|
+
allowedOperations: ['read_posts', 'create_post', 'login'],
|
|
29
|
+
maxContentLength: 2000,
|
|
30
|
+
rateLimitWindow: 3600000, // 1 hour in ms
|
|
31
|
+
},
|
|
32
|
+
permissions: {
|
|
33
|
+
canCreatePosts: true,
|
|
34
|
+
canReadPosts: true,
|
|
35
|
+
canAccessFeed: true,
|
|
36
|
+
canAccessAgentProfiles: true,
|
|
37
|
+
canUsePrompts: true,
|
|
38
|
+
canUseSampling: true,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
this.roots.set(defaultRoot.uri, defaultRoot);
|
|
43
|
+
logger.info('Roots manager initialized', { rootCount: this.roots.size });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get root definition for a session
|
|
48
|
+
*/
|
|
49
|
+
getRootForSession(sessionId: string): RootDefinition | undefined {
|
|
50
|
+
const rootUri = this.sessionRootMap.get(sessionId) || 'social://workspace';
|
|
51
|
+
return this.roots.get(rootUri);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Assign a root to a session
|
|
56
|
+
*/
|
|
57
|
+
assignRootToSession(sessionId: string, rootUri: string): boolean {
|
|
58
|
+
if (!this.roots.has(rootUri)) {
|
|
59
|
+
logger.warn('Attempted to assign non-existent root', { sessionId, rootUri });
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.sessionRootMap.set(sessionId, rootUri);
|
|
64
|
+
logger.debug('Assigned root to session', { sessionId, rootUri });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if an operation is allowed for a session
|
|
70
|
+
*/
|
|
71
|
+
isOperationAllowed(sessionId: string, operation: string): boolean {
|
|
72
|
+
const root = this.getRootForSession(sessionId);
|
|
73
|
+
if (!root) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return root.limits.allowedOperations.includes(operation);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if content length is within limits
|
|
82
|
+
*/
|
|
83
|
+
isContentLengthValid(sessionId: string, contentLength: number): boolean {
|
|
84
|
+
const root = this.getRootForSession(sessionId);
|
|
85
|
+
if (!root) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return contentLength <= root.limits.maxContentLength;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get all available roots
|
|
94
|
+
*/
|
|
95
|
+
getAllRoots(): RootDefinition[] {
|
|
96
|
+
return Array.from(this.roots.values());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Add a new root definition
|
|
101
|
+
*/
|
|
102
|
+
addRoot(root: RootDefinition): void {
|
|
103
|
+
this.roots.set(root.uri, root);
|
|
104
|
+
logger.info('Added new root', { uri: root.uri, name: root.name });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Remove a session's root assignment
|
|
109
|
+
*/
|
|
110
|
+
clearSessionRoot(sessionId: string): void {
|
|
111
|
+
this.sessionRootMap.delete(sessionId);
|
|
112
|
+
logger.debug('Cleared session root assignment', { sessionId });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function registerRoots(server: McpServer, context: RootsContext) {
|
|
117
|
+
const rootsManager = new RootsManager();
|
|
118
|
+
|
|
119
|
+
// Add the roots manager to the context for other modules to use
|
|
120
|
+
(context as any).rootsManager = rootsManager;
|
|
121
|
+
|
|
122
|
+
// Register roots as a resource
|
|
123
|
+
server.resource(
|
|
124
|
+
'workspace-roots',
|
|
125
|
+
'social://roots',
|
|
126
|
+
{
|
|
127
|
+
description: 'Available workspace boundaries and operational limits',
|
|
128
|
+
mimeType: 'application/json',
|
|
129
|
+
},
|
|
130
|
+
async () => {
|
|
131
|
+
try {
|
|
132
|
+
logger.debug('Processing roots resource request');
|
|
133
|
+
|
|
134
|
+
const roots = rootsManager.getAllRoots();
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
contents: [
|
|
138
|
+
{
|
|
139
|
+
uri: 'social://roots',
|
|
140
|
+
text: JSON.stringify(
|
|
141
|
+
{
|
|
142
|
+
roots: roots.map((root) => ({
|
|
143
|
+
uri: root.uri,
|
|
144
|
+
name: root.name,
|
|
145
|
+
description: root.description,
|
|
146
|
+
})),
|
|
147
|
+
},
|
|
148
|
+
null,
|
|
149
|
+
2,
|
|
150
|
+
),
|
|
151
|
+
mimeType: 'application/json',
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
} catch (error) {
|
|
156
|
+
logger.error('Error in roots resource', {
|
|
157
|
+
error: error instanceof Error ? error.message : String(error),
|
|
158
|
+
});
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
logger.info('Roots resource registered');
|
|
165
|
+
return rootsManager;
|
|
166
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// ABOUTME: Type definitions for MCP Roots functionality
|
|
2
|
+
// ABOUTME: Defines workspace boundaries, limits, and permissions for multi-tenant operation
|
|
3
|
+
|
|
4
|
+
export interface RootLimits {
|
|
5
|
+
maxPostsPerHour: number;
|
|
6
|
+
maxReadRequestsPerMinute: number;
|
|
7
|
+
maxConcurrentSessions: number;
|
|
8
|
+
allowedOperations: string[];
|
|
9
|
+
maxContentLength: number;
|
|
10
|
+
rateLimitWindow: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RootPermissions {
|
|
14
|
+
canCreatePosts: boolean;
|
|
15
|
+
canReadPosts: boolean;
|
|
16
|
+
canAccessFeed: boolean;
|
|
17
|
+
canAccessAgentProfiles: boolean;
|
|
18
|
+
canUsePrompts: boolean;
|
|
19
|
+
canUseSampling: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RootDefinition {
|
|
23
|
+
uri: string;
|
|
24
|
+
name: string;
|
|
25
|
+
description: string;
|
|
26
|
+
limits: RootLimits;
|
|
27
|
+
permissions: RootPermissions;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface RootListResponse {
|
|
31
|
+
roots: Array<{
|
|
32
|
+
uri: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
}>;
|
|
36
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
// ABOUTME: Session management for tracking logged-in agents per connection
|
|
2
|
+
// ABOUTME: Provides in-memory storage and utilities for session handling
|
|
3
|
+
|
|
4
|
+
import type { Session } from './types.js';
|
|
5
|
+
|
|
6
|
+
export class SessionManager {
|
|
7
|
+
private sessions: Map<string, Session>;
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
this.sessions = new Map();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Creates a new session or updates an existing one
|
|
15
|
+
*/
|
|
16
|
+
async createSession(sessionId: string, agentName: string): Promise<Session> {
|
|
17
|
+
return this.createSessionUnsafe(sessionId, agentName);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new session or updates an existing one without locking
|
|
22
|
+
*/
|
|
23
|
+
private createSessionUnsafe(sessionId: string, agentName: string): Session {
|
|
24
|
+
const now = new Date();
|
|
25
|
+
const expiresAt = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24 hours from now
|
|
26
|
+
const session: Session = {
|
|
27
|
+
sessionId,
|
|
28
|
+
agentName,
|
|
29
|
+
loginTimestamp: now,
|
|
30
|
+
lastActivity: now,
|
|
31
|
+
expiresAt,
|
|
32
|
+
isValid: true,
|
|
33
|
+
};
|
|
34
|
+
this.sessions.set(sessionId, session);
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves a session by ID if valid, otherwise returns undefined
|
|
40
|
+
*/
|
|
41
|
+
getSession(sessionId: string): Session | undefined {
|
|
42
|
+
if (!this.hasValidSession(sessionId)) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
return this.sessions.get(sessionId);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Updates session activity timestamp for valid sessions
|
|
50
|
+
*/
|
|
51
|
+
updateSessionActivity(sessionId: string): boolean {
|
|
52
|
+
const session = this.sessions.get(sessionId);
|
|
53
|
+
if (!session || !this.hasValidSession(sessionId)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
session.lastActivity = new Date();
|
|
58
|
+
this.sessions.set(sessionId, session);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Deletes a session by ID
|
|
64
|
+
*/
|
|
65
|
+
async deleteSession(sessionId: string): Promise<boolean> {
|
|
66
|
+
return this.sessions.delete(sessionId);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Checks if a valid session exists with proper expiration and validation checks
|
|
71
|
+
*/
|
|
72
|
+
hasValidSession(sessionId: string): boolean {
|
|
73
|
+
const session = this.sessions.get(sessionId);
|
|
74
|
+
if (!session) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const now = new Date();
|
|
79
|
+
|
|
80
|
+
// Check if session is marked as invalid
|
|
81
|
+
if (!session.isValid) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check if session has expired
|
|
86
|
+
if (now > session.expiresAt) {
|
|
87
|
+
// Auto-invalidate expired session
|
|
88
|
+
session.isValid = false;
|
|
89
|
+
this.sessions.set(sessionId, session);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if session has been inactive for too long (4 hours)
|
|
94
|
+
const inactiveThreshold = 4 * 60 * 60 * 1000; // 4 hours
|
|
95
|
+
if (now.getTime() - session.lastActivity.getTime() > inactiveThreshold) {
|
|
96
|
+
session.isValid = false;
|
|
97
|
+
this.sessions.set(sessionId, session);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Gets all active sessions (for debugging/monitoring)
|
|
106
|
+
*/
|
|
107
|
+
getAllSessions(): Session[] {
|
|
108
|
+
return Array.from(this.sessions.values());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Clears all sessions
|
|
113
|
+
*/
|
|
114
|
+
async clearAllSessions(): Promise<void> {
|
|
115
|
+
this.sessions.clear();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Gets the number of active sessions
|
|
120
|
+
*/
|
|
121
|
+
getSessionCount(): number {
|
|
122
|
+
return this.sessions.size;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Cleans up sessions older than the specified age in milliseconds
|
|
127
|
+
*/
|
|
128
|
+
async cleanupOldSessions(maxAgeMs: number): Promise<number> {
|
|
129
|
+
return this.cleanupOldSessionsUnsafe(maxAgeMs);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Cleans up sessions older than the specified age in milliseconds without locking
|
|
134
|
+
*/
|
|
135
|
+
private cleanupOldSessionsUnsafe(maxAgeMs: number): number {
|
|
136
|
+
const now = new Date();
|
|
137
|
+
let removedCount = 0;
|
|
138
|
+
|
|
139
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
140
|
+
const age = now.getTime() - session.loginTimestamp.getTime();
|
|
141
|
+
if (age > maxAgeMs) {
|
|
142
|
+
this.sessions.delete(sessionId);
|
|
143
|
+
removedCount++;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return removedCount;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// ABOUTME: Create post tool implementation for creating new social media posts
|
|
2
|
+
// ABOUTME: Requires session validation and integrates with the API client
|
|
3
|
+
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import type { IApiClient } from '../api-client.js';
|
|
6
|
+
import { config } from '../config.js';
|
|
7
|
+
import type { SessionManager } from '../session-manager.js';
|
|
8
|
+
import type { CreatePostToolResponse } from '../types.js';
|
|
9
|
+
import { safeJsonStringify } from '../utils/json.js';
|
|
10
|
+
import { validateCreatePostInput } from '../validation.js';
|
|
11
|
+
|
|
12
|
+
export const createPostInputSchema = z.object({
|
|
13
|
+
content: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.describe(
|
|
17
|
+
"The content of the post. Share updates frequently - your followers want to know what you're up to!",
|
|
18
|
+
),
|
|
19
|
+
tags: z.array(z.string()).optional().describe('Optional tags for the post'),
|
|
20
|
+
parent_post_id: z.string().optional().describe('ID of the post to reply to (optional)'),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const createPostToolSchema = {
|
|
24
|
+
description:
|
|
25
|
+
'Create a new post or reply within the team. Post regularly to keep your social media active and engaging!',
|
|
26
|
+
inputSchema: {
|
|
27
|
+
content: z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1)
|
|
30
|
+
.describe(
|
|
31
|
+
"The content of the post. Share updates frequently - your followers want to know what you're up to!",
|
|
32
|
+
),
|
|
33
|
+
tags: z.array(z.string()).optional().describe('Optional tags for the post'),
|
|
34
|
+
parent_post_id: z.string().optional().describe('ID of the post to reply to (optional)'),
|
|
35
|
+
},
|
|
36
|
+
annotations: {
|
|
37
|
+
title: 'Create Social Media Post',
|
|
38
|
+
readOnlyHint: false,
|
|
39
|
+
destructiveHint: false,
|
|
40
|
+
idempotentHint: false,
|
|
41
|
+
openWorldHint: true,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export interface CreatePostToolContext {
|
|
46
|
+
sessionManager: SessionManager;
|
|
47
|
+
apiClient: IApiClient;
|
|
48
|
+
getSessionId: () => string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Infer the input type from Zod schema
|
|
52
|
+
type CreatePostInput = z.infer<typeof createPostInputSchema>;
|
|
53
|
+
|
|
54
|
+
export async function createPostToolHandler(
|
|
55
|
+
input: CreatePostInput,
|
|
56
|
+
context: CreatePostToolContext,
|
|
57
|
+
): Promise<{ content: Array<{ type: 'text'; text: string }> }> {
|
|
58
|
+
try {
|
|
59
|
+
// Validate input
|
|
60
|
+
const validation = validateCreatePostInput(input);
|
|
61
|
+
if (!validation.isValid) {
|
|
62
|
+
const response: CreatePostToolResponse = {
|
|
63
|
+
success: false,
|
|
64
|
+
error: 'Invalid input',
|
|
65
|
+
details: validation.errors
|
|
66
|
+
.map((e) => `${e.field || 'unknown'}: ${e.message || 'unknown error'}`)
|
|
67
|
+
.join(', '),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: safeJsonStringify(response),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!validation.data) {
|
|
81
|
+
throw new Error('Validation succeeded but data is missing');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const { content, tags, parent_post_id } = validation.data;
|
|
85
|
+
|
|
86
|
+
// Note: Empty tags will be filtered out later during processing
|
|
87
|
+
|
|
88
|
+
// Get session ID and check if user is logged in
|
|
89
|
+
const sessionId = context.getSessionId();
|
|
90
|
+
const session = context.sessionManager.getSession(sessionId);
|
|
91
|
+
|
|
92
|
+
if (!session) {
|
|
93
|
+
const response: CreatePostToolResponse = {
|
|
94
|
+
success: false,
|
|
95
|
+
error: 'Authentication required',
|
|
96
|
+
details: 'You must be logged in to create posts',
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
content: [
|
|
101
|
+
{
|
|
102
|
+
type: 'text',
|
|
103
|
+
text: safeJsonStringify(response),
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Note: Parent post validation removed for performance.
|
|
110
|
+
// The API server will handle invalid parent_post_id gracefully.
|
|
111
|
+
|
|
112
|
+
// Prepare post data
|
|
113
|
+
const postData = {
|
|
114
|
+
author_name: session.agentName,
|
|
115
|
+
content: content,
|
|
116
|
+
tags: tags?.length > 0 ? tags : undefined,
|
|
117
|
+
parent_post_id: parent_post_id,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Call API to create post
|
|
121
|
+
const apiResponse = await context.apiClient.createPost(config.teamName, postData);
|
|
122
|
+
|
|
123
|
+
// Return successful response
|
|
124
|
+
const response: CreatePostToolResponse = {
|
|
125
|
+
success: true,
|
|
126
|
+
post: apiResponse.post,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: safeJsonStringify(response),
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
};
|
|
137
|
+
} catch (error) {
|
|
138
|
+
// Handle API errors
|
|
139
|
+
const response: CreatePostToolResponse = {
|
|
140
|
+
success: false,
|
|
141
|
+
error: 'Failed to create post',
|
|
142
|
+
details: error instanceof Error ? error.message : 'Unknown error',
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
content: [
|
|
147
|
+
{
|
|
148
|
+
type: 'text',
|
|
149
|
+
text: safeJsonStringify(response),
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// ABOUTME: Main tool registration for MCP tools
|
|
2
|
+
// ABOUTME: Consolidates tool registration logic for reuse across transports
|
|
3
|
+
|
|
4
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
import type { z } from 'zod';
|
|
6
|
+
import type { IApiClient } from '../api-client.js';
|
|
7
|
+
import { logger } from '../logger.js';
|
|
8
|
+
import type { SessionManager } from '../session-manager.js';
|
|
9
|
+
import {
|
|
10
|
+
type createPostInputSchema,
|
|
11
|
+
createPostToolHandler,
|
|
12
|
+
createPostToolSchema,
|
|
13
|
+
} from './create-post.js';
|
|
14
|
+
import { type loginInputSchema, loginToolHandler, loginToolSchema } from './login.js';
|
|
15
|
+
import {
|
|
16
|
+
type readPostsInputSchema,
|
|
17
|
+
readPostsToolHandler,
|
|
18
|
+
readPostsToolSchema,
|
|
19
|
+
} from './read-posts.js';
|
|
20
|
+
|
|
21
|
+
export interface ToolContext {
|
|
22
|
+
sessionManager: SessionManager;
|
|
23
|
+
apiClient: IApiClient;
|
|
24
|
+
hooksManager?: any;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Register all tools with the MCP server
|
|
29
|
+
*/
|
|
30
|
+
export function registerTools(server: McpServer, context: ToolContext): void {
|
|
31
|
+
logger.info('Registering MCP tools');
|
|
32
|
+
|
|
33
|
+
// Register the login tool
|
|
34
|
+
server.registerTool('login', loginToolSchema, async (args, _mcpContext) => {
|
|
35
|
+
// Create context for the login tool - use a global session for this MCP server instance
|
|
36
|
+
const toolContext = {
|
|
37
|
+
sessionManager: context.sessionManager,
|
|
38
|
+
getSessionId: () => 'global-session',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
return loginToolHandler(args as z.infer<typeof loginInputSchema>, toolContext);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Register the read_posts tool
|
|
45
|
+
server.registerTool('read_posts', readPostsToolSchema, async (args, _mcpContext) => {
|
|
46
|
+
// Create context for the read posts tool
|
|
47
|
+
const toolContext = {
|
|
48
|
+
apiClient: context.apiClient,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return readPostsToolHandler(args as z.infer<typeof readPostsInputSchema>, toolContext);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Register the create_post tool
|
|
55
|
+
server.registerTool('create_post', createPostToolSchema, async (args, _mcpContext) => {
|
|
56
|
+
// Create context for the create post tool - use same global session
|
|
57
|
+
const toolContext = {
|
|
58
|
+
sessionManager: context.sessionManager,
|
|
59
|
+
apiClient: context.apiClient,
|
|
60
|
+
getSessionId: () => 'global-session',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return createPostToolHandler(args as z.infer<typeof createPostInputSchema>, toolContext);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
logger.info('Tools registered', {
|
|
67
|
+
count: 3,
|
|
68
|
+
tools: ['login', 'read_posts', 'create_post'],
|
|
69
|
+
});
|
|
70
|
+
}
|