@ophirai/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,87 @@
1
+ /** @module @ophirai/mcp-server — MCP tool wrapper for Ophir negotiation protocol */
2
+ import type { SellerInfo, Agreement } from '@ophirai/sdk';
3
+ /** JSON Schema property descriptor for MCP tool input schemas. */
4
+ interface JsonSchemaProperty {
5
+ type: string;
6
+ description?: string;
7
+ enum?: string[];
8
+ }
9
+ /** Definition of an MCP tool with name, description, and input schema. */
10
+ export interface MCPToolDefinition {
11
+ name: string;
12
+ description: string;
13
+ inputSchema: {
14
+ type: 'object';
15
+ properties: Record<string, JsonSchemaProperty>;
16
+ required?: string[];
17
+ };
18
+ }
19
+ export declare const TOOLS: MCPToolDefinition[];
20
+ /** Input parameters for the negotiate_service MCP tool. */
21
+ export interface NegotiateServiceInput {
22
+ service_category: string;
23
+ requirements?: Record<string, unknown>;
24
+ max_budget: string;
25
+ currency?: string;
26
+ }
27
+ /** Input parameters for the check_agreement_status MCP tool. */
28
+ export interface CheckAgreementStatusInput {
29
+ agreement_id: string;
30
+ }
31
+ /** Standard MCP tool result with text content. */
32
+ export interface MCPToolResult {
33
+ content: {
34
+ type: 'text';
35
+ text: string;
36
+ }[];
37
+ isError?: boolean;
38
+ }
39
+ /** Configuration for the Ophir MCP server. */
40
+ export interface OphirMCPServerConfig {
41
+ /** Known seller agents to negotiate with. */
42
+ sellers: SellerInfo[];
43
+ /** Buyer endpoint for sending RFQs (defaults to http://localhost:3001). */
44
+ buyerEndpoint?: string;
45
+ /** In-memory store of active agreements. */
46
+ agreements?: Map<string, Agreement>;
47
+ }
48
+ /** Send an RFQ, collect quotes, and return the best option. */
49
+ export declare function handleNegotiateService(input: NegotiateServiceInput, config: OphirMCPServerConfig): Promise<MCPToolResult>;
50
+ /** Look up a previously negotiated agreement by ID. */
51
+ export declare function handleCheckAgreementStatus(input: CheckAgreementStatusInput, config: OphirMCPServerConfig): Promise<MCPToolResult>;
52
+ /** List all available service categories and their known sellers. */
53
+ export declare function handleListServices(config: OphirMCPServerConfig): MCPToolResult;
54
+ /** JSON-RPC 2.0 request envelope for MCP. */
55
+ interface McpJsonRpcRequest {
56
+ jsonrpc: '2.0';
57
+ id: string | number;
58
+ method: string;
59
+ params?: {
60
+ name?: string;
61
+ arguments?: Record<string, unknown>;
62
+ };
63
+ }
64
+ /** JSON-RPC 2.0 response envelope for MCP. */
65
+ interface McpJsonRpcResponse {
66
+ jsonrpc: '2.0';
67
+ id: string | number;
68
+ result?: unknown;
69
+ error?: {
70
+ code: number;
71
+ message: string;
72
+ data?: unknown;
73
+ };
74
+ }
75
+ /**
76
+ * MCP server that exposes Ophir negotiation capabilities as MCP tools.
77
+ *
78
+ * Supports three tools: negotiate_service, check_agreement_status, and list_services.
79
+ */
80
+ export declare class OphirMCPServer {
81
+ private config;
82
+ constructor(config: OphirMCPServerConfig);
83
+ /** Handle an incoming MCP JSON-RPC request and return the response. */
84
+ handleRequest(req: McpJsonRpcRequest): Promise<McpJsonRpcResponse>;
85
+ private callTool;
86
+ }
87
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,198 @@
1
+ export const TOOLS = [
2
+ {
3
+ name: 'negotiate_service',
4
+ description: 'Send an RFQ to known sellers for a service category, collect quotes, and return the best option.',
5
+ inputSchema: {
6
+ type: 'object',
7
+ properties: {
8
+ service_category: {
9
+ type: 'string',
10
+ description: 'Category of service needed (e.g. inference, translation, code_review)',
11
+ },
12
+ requirements: {
13
+ type: 'object',
14
+ description: 'Optional specific requirements for the service',
15
+ },
16
+ max_budget: {
17
+ type: 'string',
18
+ description: 'Maximum price per unit willing to pay',
19
+ },
20
+ currency: {
21
+ type: 'string',
22
+ description: 'Payment currency (default: USDC)',
23
+ },
24
+ },
25
+ required: ['service_category', 'max_budget'],
26
+ },
27
+ },
28
+ {
29
+ name: 'check_agreement_status',
30
+ description: 'Check the current status of a negotiation or agreement.',
31
+ inputSchema: {
32
+ type: 'object',
33
+ properties: {
34
+ agreement_id: {
35
+ type: 'string',
36
+ description: 'The agreement ID to check',
37
+ },
38
+ },
39
+ required: ['agreement_id'],
40
+ },
41
+ },
42
+ {
43
+ name: 'list_services',
44
+ description: 'List available service categories and known sellers.',
45
+ inputSchema: {
46
+ type: 'object',
47
+ properties: {},
48
+ },
49
+ },
50
+ ];
51
+ /** Wrap a text string as an MCP tool result. */
52
+ function textResult(text, isError = false) {
53
+ return { content: [{ type: 'text', text }], isError };
54
+ }
55
+ /** Send an RFQ, collect quotes, and return the best option. */
56
+ export async function handleNegotiateService(input, config) {
57
+ const { BuyerAgent } = await import('@ophirai/sdk');
58
+ const matchingSellers = config.sellers.filter((s) => s.services.some((svc) => svc.category === input.service_category));
59
+ if (matchingSellers.length === 0) {
60
+ return textResult(`No sellers found for service category: ${input.service_category}`, true);
61
+ }
62
+ const buyer = new BuyerAgent({
63
+ endpoint: config.buyerEndpoint ?? 'http://localhost:3001',
64
+ });
65
+ try {
66
+ const session = await buyer.requestQuotes({
67
+ service: {
68
+ category: input.service_category,
69
+ requirements: input.requirements,
70
+ },
71
+ budget: {
72
+ max_price_per_unit: input.max_budget,
73
+ currency: input.currency ?? 'USDC',
74
+ unit: 'request',
75
+ },
76
+ sellers: matchingSellers.map((s) => s.endpoint),
77
+ });
78
+ const quotes = await buyer.waitForQuotes(session, {
79
+ timeout: 30_000,
80
+ minQuotes: 1,
81
+ });
82
+ if (quotes.length === 0) {
83
+ return textResult('No quotes received within timeout.', true);
84
+ }
85
+ const ranked = buyer.rankQuotes(quotes, 'cheapest');
86
+ const best = ranked[0];
87
+ return textResult(JSON.stringify({
88
+ best_quote: {
89
+ seller: best.seller.agent_id,
90
+ price: best.pricing.price_per_unit,
91
+ currency: best.pricing.currency,
92
+ unit: best.pricing.unit,
93
+ sla: best.sla_offered?.metrics?.map((m) => ({
94
+ metric: m.name,
95
+ target: m.target,
96
+ })),
97
+ },
98
+ total_quotes: quotes.length,
99
+ rfq_id: session.rfqId,
100
+ }, null, 2));
101
+ }
102
+ catch (err) {
103
+ const msg = err instanceof Error ? err.message : String(err);
104
+ return textResult(`Negotiation failed: ${msg}`, true);
105
+ }
106
+ finally {
107
+ buyer.close();
108
+ }
109
+ }
110
+ /** Look up a previously negotiated agreement by ID. */
111
+ export async function handleCheckAgreementStatus(input, config) {
112
+ const agreement = config.agreements?.get(input.agreement_id);
113
+ if (!agreement) {
114
+ return textResult(`No agreement found with ID: ${input.agreement_id}`, true);
115
+ }
116
+ return textResult(JSON.stringify({
117
+ agreement_id: agreement.agreement_id,
118
+ rfq_id: agreement.rfq_id,
119
+ price: agreement.final_terms.price_per_unit,
120
+ currency: agreement.final_terms.currency,
121
+ unit: agreement.final_terms.unit,
122
+ escrow: agreement.escrow ?? null,
123
+ sla_metrics: agreement.final_terms.sla?.metrics?.length ?? 0,
124
+ }, null, 2));
125
+ }
126
+ /** List all available service categories and their known sellers. */
127
+ export function handleListServices(config) {
128
+ const categories = new Map();
129
+ for (const seller of config.sellers) {
130
+ for (const svc of seller.services) {
131
+ const entry = categories.get(svc.category) ?? {
132
+ sellers: [],
133
+ price_range: [],
134
+ };
135
+ entry.sellers.push(seller.agentId);
136
+ entry.price_range.push(`${svc.base_price} ${svc.currency}/${svc.unit}`);
137
+ categories.set(svc.category, entry);
138
+ }
139
+ }
140
+ const services = Array.from(categories.entries()).map(([cat, info]) => ({
141
+ category: cat,
142
+ seller_count: info.sellers.length,
143
+ prices: info.price_range,
144
+ }));
145
+ return textResult(JSON.stringify({ services }, null, 2));
146
+ }
147
+ function rpcResult(id, result) {
148
+ return { jsonrpc: '2.0', id, result };
149
+ }
150
+ function rpcError(id, code, message) {
151
+ return { jsonrpc: '2.0', id, error: { code, message } };
152
+ }
153
+ /**
154
+ * MCP server that exposes Ophir negotiation capabilities as MCP tools.
155
+ *
156
+ * Supports three tools: negotiate_service, check_agreement_status, and list_services.
157
+ */
158
+ export class OphirMCPServer {
159
+ config;
160
+ constructor(config) {
161
+ this.config = {
162
+ ...config,
163
+ agreements: config.agreements ?? new Map(),
164
+ };
165
+ }
166
+ /** Handle an incoming MCP JSON-RPC request and return the response. */
167
+ async handleRequest(req) {
168
+ switch (req.method) {
169
+ case 'tools/list':
170
+ return rpcResult(req.id, { tools: TOOLS });
171
+ case 'tools/call': {
172
+ const name = req.params?.name ?? '';
173
+ const args = req.params?.arguments ?? {};
174
+ return this.callTool(req.id, name, args);
175
+ }
176
+ case 'initialize':
177
+ return rpcResult(req.id, {
178
+ protocolVersion: '2024-11-05',
179
+ capabilities: { tools: {} },
180
+ serverInfo: { name: '@ophirai/mcp-server', version: '0.1.0' },
181
+ });
182
+ default:
183
+ return rpcError(req.id, -32601, `Method not found: ${req.method}`);
184
+ }
185
+ }
186
+ async callTool(id, name, args) {
187
+ switch (name) {
188
+ case 'negotiate_service':
189
+ return rpcResult(id, await handleNegotiateService(args, this.config));
190
+ case 'check_agreement_status':
191
+ return rpcResult(id, await handleCheckAgreementStatus(args, this.config));
192
+ case 'list_services':
193
+ return rpcResult(id, handleListServices(this.config));
194
+ default:
195
+ return rpcError(id, -32602, `Unknown tool: ${name}`);
196
+ }
197
+ }
198
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@ophirai/mcp-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP tool server exposing Ophir agent negotiation as Model Context Protocol tools for LLM-powered agents",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/ophirai/ophir",
12
+ "directory": "packages/mcp-server"
13
+ },
14
+ "keywords": ["mcp", "model-context-protocol", "ai-agents", "negotiation", "ophir", "llm-tools"],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "test": "vitest run --passWithNoTests",
18
+ "lint": "tsc --noEmit",
19
+ "clean": "rm -rf dist"
20
+ },
21
+ "files": ["dist", "README.md"],
22
+ "dependencies": {
23
+ "@ophirai/sdk": "*",
24
+ "@ophirai/protocol": "*"
25
+ },
26
+ "devDependencies": {
27
+ "typescript": "^5.5.0",
28
+ "vitest": "^2.0.0"
29
+ }
30
+ }