diff --git a/src/box/box.cc b/src/box/box.cc
index a3782415c77563fc86a314d34d1c8e781ebd47bf..b2a795b30c7ac136cb010014910d2224ea716bca 100644
--- a/src/box/box.cc
+++ b/src/box/box.cc
@@ -67,6 +67,7 @@
 #include "gc.h"
 #include "checkpoint.h"
 #include "systemd.h"
+#include "call.h"
 
 static char status[64] = "unknown";
 
diff --git a/src/box/box.h b/src/box/box.h
index 5164054327c9ceb70617257c7137bc04c64ad071..460c064772d372b8df141c1aff94f6a40a73a4f4 100644
--- a/src/box/box.h
+++ b/src/box/box.h
@@ -154,11 +154,6 @@ void box_update_vinyl_options(void);
 extern "C" {
 #endif /* defined(__cplusplus) */
 
-struct box_function_ctx {
-	struct request *request;
-	struct port *port;
-};
-
 typedef struct tuple box_tuple_t;
 
 /* box_select is private and used only by FFI */
diff --git a/src/box/call.cc b/src/box/call.cc
index 286b34ca71ceffdbef8f93b8d0f9ceda5c9437a9..c6fd46c36d166cdcce70e76ccd47e67c65fc466b 100644
--- a/src/box/call.cc
+++ b/src/box/call.cc
@@ -43,6 +43,71 @@
 #include "rmean.h"
 #include "small/obuf.h"
 
+static const char empty_args[] = { (char)0x90 };
+
+void
+call_request_decode_xc(struct call_request *request, uint8_t type,
+		       uint64_t sync, const char *data, uint32_t len)
+{
+	assert(type == IPROTO_CALL || type == IPROTO_CALL_16 || type == IPROTO_EVAL);
+	const char *end = data + len;
+	uint32_t map_size;
+	if (mp_typeof(*data) != MP_MAP || mp_check_map(data, end) > 0)
+		tnt_raise(ClientError, ER_INVALID_MSGPACK, "packet body");
+
+	map_size = mp_decode_map(&data);
+	request->name = NULL;
+	request->args = NULL;
+	request->args_end = NULL;
+	request->type = type;
+	request->sync = sync;
+	for (uint32_t i = 0; i < map_size; ++i) {
+		uint8_t key = *data;
+		if (key != IPROTO_EXPR && key != IPROTO_FUNCTION_NAME &&
+		    key != IPROTO_TUPLE) {
+			/* Skip key + value. */
+			mp_check(&data, end);
+			mp_check(&data, end);
+			continue;
+		}
+		const char *value = ++data;
+		if (mp_check(&data, end) != 0) {
+			tnt_raise(ClientError, ER_INVALID_MSGPACK,
+				  "packet body");
+		}
+		switch(key) {
+		case IPROTO_EXPR:
+			request->expr = value;
+			break;
+		case IPROTO_FUNCTION_NAME:
+			request->name = value;
+			break;
+		case IPROTO_TUPLE:
+			request->args = value;
+			request->args_end = data;
+			break;
+		default:
+			unreachable();
+		}
+	}
+	if (type == IPROTO_EVAL) {
+		if (request->expr == NULL) {
+			tnt_raise(ClientError, ER_MISSING_REQUEST_FIELD,
+				  iproto_key_name(IPROTO_EXPR));
+		}
+	} else if (request->name == NULL) {
+		assert(type == IPROTO_CALL_16 || type == IPROTO_CALL);
+		tnt_raise(ClientError, ER_MISSING_REQUEST_FIELD,
+			  iproto_key_name(IPROTO_FUNCTION_NAME));
+	}
+	if (request->args == NULL) {
+		request->args = empty_args;
+		request->args_end = empty_args + sizeof(empty_args);
+	}
+	if (data != end)
+		tnt_raise(ClientError, ER_INVALID_MSGPACK, "packet body");
+}
+
 static inline struct func *
 access_check_func(const char *name, uint32_t name_len)
 {
@@ -69,7 +134,7 @@ access_check_func(const char *name, uint32_t name_len)
 }
 
 static int
-func_call(struct func *func, struct request *request, struct obuf *out)
+func_call(struct func *func, struct call_request *request, struct obuf *out)
 {
 	assert(func != NULL && func->def->language == FUNC_LANGUAGE_C);
 	if (func->func == NULL)
@@ -85,7 +150,7 @@ func_call(struct func *func, struct request *request, struct obuf *out)
 	diag_clear(&fiber()->diag);
 	assert(!in_txn()); /* transaction is not started */
 	/* Call function from the shared library */
-	int rc = func->func(&ctx, request->tuple, request->tuple_end);
+	int rc = func->func(&ctx, request->args, request->args_end);
 	if (rc != 0) {
 		if (diag_last_error(&fiber()->diag) == NULL) {
 			/* Stored procedure forget to set diag  */
@@ -105,7 +170,7 @@ func_call(struct func *func, struct request *request, struct obuf *out)
 			obuf_rollback_to_svp(out, &svp);
 			goto error;
 		}
-		iproto_reply_select(out, &svp, request->header->sync,
+		iproto_reply_select(out, &svp, request->sync,
 				    ::schema_version, port.size);
 	} else {
 		assert(request->type == IPROTO_CALL);
@@ -118,7 +183,7 @@ func_call(struct func *func, struct request *request, struct obuf *out)
 			obuf_rollback_to_svp(out, &svp);
 			goto error;
 		}
-		iproto_reply_select(out, &svp, request->header->sync,
+		iproto_reply_select(out, &svp, request->sync,
 				    ::schema_version, 1);
 	}
 
@@ -130,13 +195,14 @@ func_call(struct func *func, struct request *request, struct obuf *out)
 }
 
 void
-box_process_call(struct request *request, struct obuf *out)
+box_process_call(struct call_request *request, struct obuf *out)
 {
 	rmean_collect(rmean_box, IPROTO_CALL, 1);
 	/**
 	 * Find the function definition and check access.
 	 */
-	const char *name = request->key;
+	const char *name = request->name;
+	assert(name != NULL);
 	uint32_t name_len = mp_decode_strl(&name);
 	struct func *func = access_check_func(name, name_len);
 	/*
@@ -192,7 +258,7 @@ box_process_call(struct request *request, struct obuf *out)
 }
 
 void
-box_process_eval(struct request *request, struct obuf *out)
+box_process_eval(struct call_request *request, struct obuf *out)
 {
 	rmean_collect(rmean_box, IPROTO_EVAL, 1);
 	/* Check permissions */
@@ -204,7 +270,8 @@ box_process_eval(struct request *request, struct obuf *out)
 
 	if (in_txn()) {
 		/* The procedure forgot to call box.commit() */
-		const char *expr = request->key;
+		const char *expr = request->expr;
+		assert(expr != NULL);
 		uint32_t expr_len = mp_decode_strl(&expr);
 		say_warn("a transaction is active at return from EVAL '%.*s'",
 			expr_len, expr);
diff --git a/src/box/call.h b/src/box/call.h
index 5213de778ed27d0e11449a5419931f22884a15eb..1c1cc6c4ba44072d404ceac27f87c5d6cef58e08 100644
--- a/src/box/call.h
+++ b/src/box/call.h
@@ -31,13 +31,50 @@
  * SUCH DAMAGE.
  */
 
-struct request;
+#include <stdint.h>
+
 struct obuf;
 
+/** Request details of call and eval. */
+struct call_request {
+	/**
+	 * Set either name to function name of call request or
+	 * set expr to lua expression for eval request.
+	 */
+	union {
+		const char *name;
+		const char *expr;
+	};
+	/** Call/eval parameters. */
+	const char *args;
+	const char *args_end;
+	/** IPROTO_CALL/IPROTO_CALL_16/IPROTO_EVAL */
+	uint8_t type;
+	/** Request sync. @sa xrow_header. */
+	uint64_t sync;
+};
+
+struct box_function_ctx {
+	struct call_request *request;
+	struct port *port;
+};
+
+/**
+ * Decode call request from a given MessagePack map.
+ * @param[out] call_request Request to decode to.
+ * @param type Request type - either CALL or CALL_16 or EVAL.
+ * @param sync Request sync.
+ * @param data Request MessagePack encoded body.
+ * @param len @data length.
+ */
+void
+call_request_decode_xc(struct call_request *request, uint8_t type,
+		       uint64_t sync, const char *data, uint32_t len);
+
 void
-box_process_call(struct request *request, struct obuf *out);
+box_process_call(struct call_request *request, struct obuf *out);
 
 void
-box_process_eval(struct request *request, struct obuf *out);
+box_process_eval(struct call_request *request, struct obuf *out);
 
 #endif /* INCLUDES_TARANTOOL_MOD_BOX_CALL_H */
diff --git a/src/box/iproto.cc b/src/box/iproto.cc
index 6c31a6772e54e316102559c184541edcd7fd7cb5..74c5a9e244aed0a2fc3de6cf868e196daa872c5e 100644
--- a/src/box/iproto.cc
+++ b/src/box/iproto.cc
@@ -76,8 +76,12 @@ struct iproto_msg: public cmsg
 	/* --- Box msgs - actual requests for the transaction processor --- */
 	/* Request message code and sync. */
 	struct xrow_header header;
-	/* Box request, if this is a DML */
-	struct request request;
+	union {
+		/* Box request, if this is a DML */
+		struct request dml_request;
+		/* Box request, if this is misc (call, eval). */
+		struct call_request call_request;
+	};
 	/*
 	 * Remember the active iobuf of the connection,
 	 * in which the request is stored. The response
@@ -611,20 +615,20 @@ iproto_decode_msg(struct iproto_msg *msg, const char **pos, const char *reqend,
 {
 	xrow_header_decode_xc(&msg->header, pos, reqend);
 	assert(*pos == reqend);
-	request_create(&msg->request, msg->header.type);
-	msg->request.header = &msg->header;
+	const char *body;
+	uint8_t type = msg->header.type;
+	uint64_t sync = msg->header.sync;
 
-	switch (msg->header.type) {
+	switch (type) {
 	case IPROTO_SELECT:
 	case IPROTO_INSERT:
 	case IPROTO_REPLACE:
 	case IPROTO_UPDATE:
 	case IPROTO_DELETE:
-	case IPROTO_CALL_16:
-	case IPROTO_CALL:
 	case IPROTO_AUTH:
-	case IPROTO_EVAL:
 	case IPROTO_UPSERT:
+		request_create(&msg->dml_request, type);
+		msg->dml_request.header = &msg->header;
 		/*
 		 * This is a common request which can be parsed with
 		 * request_decode(). Parse it before putting it into
@@ -632,16 +636,24 @@ iproto_decode_msg(struct iproto_msg *msg, const char **pos, const char *reqend,
 		 * requests are parsed in tx thread into request type-
 		 * specific objects.
 		 */
-		if (msg->header.bodycnt == 0) {
-			tnt_raise(ClientError, ER_INVALID_MSGPACK,
-				  "missing request body");
-		}
-		request_decode_xc(&msg->request,
-				 (const char *) msg->header.body[0].iov_base,
-				 msg->header.body[0].iov_len,
-				 request_key_map(msg->header.type));
-		assert(msg->header.type < sizeof(dml_route)/sizeof(*dml_route));
-		cmsg_init(msg, dml_route[msg->header.type]);
+		if (msg->header.bodycnt == 0)
+			goto err_no_body;
+		body = (const char *) msg->header.body[0].iov_base;
+		request_decode_xc(&msg->dml_request, body,
+				  msg->header.body[0].iov_len,
+				  request_key_map(type));
+		assert(type < sizeof(dml_route)/sizeof(*dml_route));
+		cmsg_init(msg, dml_route[type]);
+		break;
+	case IPROTO_CALL_16:
+	case IPROTO_CALL:
+	case IPROTO_EVAL:
+		if (msg->header.bodycnt == 0)
+			goto err_no_body;
+		body = (const char *) msg->header.body[0].iov_base;
+		call_request_decode_xc(&msg->call_request, type, sync, body,
+				       msg->header.body[0].iov_len);
+		cmsg_init(msg, misc_route);
 		break;
 	case IPROTO_PING:
 		cmsg_init(msg, misc_route);
@@ -653,9 +665,12 @@ iproto_decode_msg(struct iproto_msg *msg, const char **pos, const char *reqend,
 		break;
 	default:
 		tnt_raise(ClientError, ER_UNKNOWN_REQUEST_TYPE,
-			  (uint32_t) msg->header.type);
+			  (uint32_t) type);
 		break;
 	}
+	return;
+err_no_body:
+	tnt_raise(ClientError, ER_INVALID_MSGPACK, "missing request body");
 }
 
 /** Enqueue all requests which were read up. */
@@ -940,7 +955,7 @@ tx_process1(struct cmsg *m)
 
 	struct tuple *tuple;
 	struct obuf_svp svp;
-	if (box_process1(&msg->request, &tuple) ||
+	if (box_process1(&msg->dml_request, &tuple) ||
 	    iproto_prepare_select(out, &svp))
 		goto error;
 	if (tuple && tuple_to_obuf(tuple, out))
@@ -963,7 +978,7 @@ tx_process_select(struct cmsg *m)
 	struct obuf_svp svp;
 	struct port port;
 	int rc;
-	struct request *req = &msg->request;
+	struct request *req = &msg->dml_request;
 
 	tx_fiber_init(msg->connection->session, msg->header.sync);
 
@@ -1009,16 +1024,16 @@ tx_process_misc(struct cmsg *m)
 		switch (msg->header.type) {
 		case IPROTO_CALL:
 		case IPROTO_CALL_16:
-			assert(msg->request.type == msg->header.type);
-			box_process_call(&msg->request, out);
+			assert(msg->call_request.type == msg->header.type);
+			box_process_call(&msg->call_request, out);
 			break;
 		case IPROTO_EVAL:
-			assert(msg->request.type == msg->header.type);
-			box_process_eval(&msg->request, out);
+			assert(msg->call_request.type == msg->header.type);
+			box_process_eval(&msg->call_request, out);
 			break;
 		case IPROTO_AUTH:
-			assert(msg->request.type == msg->header.type);
-			box_process_auth(&msg->request, out);
+			assert(msg->dml_request.type == msg->header.type);
+			box_process_auth(&msg->dml_request, out);
 			break;
 		case IPROTO_PING:
 			iproto_reply_ok_xc(out, msg->header.sync,
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index bfcab912f6f47af39d365dc8ed27cde388283163..62fc549b5d0a4237cc8318d173b56c6ec36af86e 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -29,6 +29,7 @@
  * SUCH DAMAGE.
  */
 #include "box/lua/call.h"
+#include "box/call.h"
 #include "box/error.h"
 #include "fiber.h"
 
@@ -243,7 +244,7 @@ luamp_encode_call(lua_State *L, struct luaL_serializer *cfg,
 }
 
 struct lua_function_ctx {
-	struct request *request;
+	struct call_request *request;
 	struct obuf *out;
 	struct obuf_svp svp;
 	/* true if `out' was changed and `svp' can be used for rollback  */
@@ -259,12 +260,12 @@ execute_lua_call(lua_State *L)
 {
 	struct lua_function_ctx *ctx = (struct lua_function_ctx *)
 		lua_topointer(L, 1);
-	struct request *request = ctx->request;
+	struct call_request *request = ctx->request;
 	struct obuf *out = ctx->out;
 	struct obuf_svp *svp = &ctx->svp;
 	lua_settop(L, 0); /* clear the stack to simplify the logic below */
 
-	const char *name = request->key;
+	const char *name = request->name;
 	uint32_t name_len = mp_decode_strl(&name);
 
 	int oc = 0; /* how many objects are on stack after box_lua_find */
@@ -272,7 +273,7 @@ execute_lua_call(lua_State *L)
 	oc = box_lua_find(L, name, name + name_len);
 
 	/* Push the rest of args (a tuple). */
-	const char *args = request->tuple;
+	const char *args = request->args;
 
 	uint32_t arg_count = mp_decode_array(&args);
 	luaL_checkstack(L, arg_count, "call: out of stack");
@@ -322,8 +323,7 @@ execute_lua_call(lua_State *L)
 	}
 
 	mpstream_flush(&stream);
-	iproto_reply_select(out, svp, request->header->sync, schema_version,
-			    count);
+	iproto_reply_select(out, svp, request->sync, schema_version, count);
 	return 0; /* truncate Lua stack */
 }
 
@@ -332,13 +332,13 @@ execute_lua_eval(lua_State *L)
 {
 	struct lua_function_ctx *ctx = (struct lua_function_ctx *)
 		lua_topointer(L, 1);
-	struct request *request = ctx->request;
+	struct call_request *request = ctx->request;
 	struct obuf *out = ctx->out;
 	struct obuf_svp *svp = &ctx->svp;
 	lua_settop(L, 0); /* clear the stack to simplify the logic below */
 
 	/* Compile expression */
-	const char *expr = request->key;
+	const char *expr = request->expr;
 	uint32_t expr_len = mp_decode_strl(&expr);
 	if (luaL_loadbuffer(L, expr, expr_len, "=eval")) {
 		diag_set(LuajitError, lua_tostring(L, -1));
@@ -346,7 +346,7 @@ execute_lua_eval(lua_State *L)
 	}
 
 	/* Unpack arguments */
-	const char *args = request->tuple;
+	const char *args = request->args;
 	uint32_t arg_count = mp_decode_array(&args);
 	luaL_checkstack(L, arg_count, "eval: out of stack");
 	for (uint32_t i = 0; i < arg_count; i++) {
@@ -368,14 +368,13 @@ execute_lua_eval(lua_State *L)
 		luamp_encode(L, luaL_msgpack_default, &stream, k);
 	}
 	mpstream_flush(&stream);
-	iproto_reply_select(out, svp, request->header->sync, schema_version,
-			    nrets);
+	iproto_reply_select(out, svp, request->sync, schema_version, nrets);
 
 	return 0;
 }
 
 static inline int
-box_process_lua(struct request *request, struct obuf *out, lua_CFunction handler)
+box_process_lua(struct call_request *request, struct obuf *out, lua_CFunction handler)
 {
 	struct lua_function_ctx ctx = { request, out, {0, 0, 0}, false };
 
@@ -402,13 +401,13 @@ box_process_lua(struct request *request, struct obuf *out, lua_CFunction handler
 }
 
 int
-box_lua_call(struct request *request, struct obuf *out)
+box_lua_call(struct call_request *request, struct obuf *out)
 {
 	return box_process_lua(request, out, execute_lua_call);
 }
 
 int
-box_lua_eval(struct request *request, struct obuf *out)
+box_lua_eval(struct call_request *request, struct obuf *out)
 {
 	return box_process_lua(request, out, execute_lua_eval);
 }
diff --git a/src/box/lua/call.h b/src/box/lua/call.h
index 4e1a3522dda847081a745980832ccf83bdbb4487..655ff893c682ffa1726fd339dc34f2cd0af72dec 100644
--- a/src/box/lua/call.h
+++ b/src/box/lua/call.h
@@ -43,7 +43,7 @@ struct lua_State;
 void
 box_lua_call_init(struct lua_State *L);
 
-struct request;
+struct call_request;
 struct obuf;
 
 /**
@@ -51,10 +51,10 @@ struct obuf;
  * (implementation of 'CALL' command code).
  */
 int
-box_lua_call(struct request *request, struct obuf *out);
+box_lua_call(struct call_request *request, struct obuf *out);
 
 int
-box_lua_eval(struct request *request, struct obuf *out);
+box_lua_eval(struct call_request *request, struct obuf *out);
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/box/xrow.h b/src/box/xrow.h
index ee1e9f09db3eeba706a70d40651707637c38692f..072bd41b69c64fc5c0c71932675076e329e31e49 100644
--- a/src/box/xrow.h
+++ b/src/box/xrow.h
@@ -254,7 +254,7 @@ struct request
 	uint32_t offset;
 	uint32_t limit;
 	uint32_t iterator;
-	/** Search key or proc name. */
+	/** Search key. */
 	const char *key;
 	const char *key_end;
 	/** Insert/replace/upsert tuple or proc argument or update operations. */
diff --git a/test/box-py/iproto.result b/test/box-py/iproto.result
index 3d1111c6d0b3f92e6fd33e05e335124a40347c23..95b39e56fa2c7b9ef81a38cae489753c3ff5d6a7 100644
--- a/test/box-py/iproto.result
+++ b/test/box-py/iproto.result
@@ -190,6 +190,17 @@ box.schema.user.grant('guest', 'read,write,execute', 'space', 'test_index_base')
 
 - [1, 0, 2, -2]
 
+function kek() return 'kek' end
+---
+...
+box.schema.user.grant('guest', 'read,write,execute', 'universe')
+---
+...
+Sync:  100
+Retcode:  [['kek']]
+box.schema.user.revoke('guest', 'read,write,execute', 'universe')
+---
+...
 space:drop()
 ---
 ...
diff --git a/test/box-py/iproto.test.py b/test/box-py/iproto.test.py
index d242a7fed8531fcedf6a955511563d9b8c944978..f545453ae7ef3b3a308562cd047760c2b7d84af0 100644
--- a/test/box-py/iproto.test.py
+++ b/test/box-py/iproto.test.py
@@ -390,5 +390,26 @@ response = Response(c, c._read_response())
 print response.__str__()
 
 c.close()
+
+#
+# gh-2619 follow up: allow empty args for call/eval.
+#
+admin("function kek() return 'kek' end")
+admin("box.schema.user.grant('guest', 'read,write,execute', 'universe')")
+
+c = Connection('localhost', server.iproto.port)
+c.connect()
+s = c._socket
+
+header = { IPROTO_CODE: REQUEST_TYPE_CALL, IPROTO_SYNC: 100 }
+body = { IPROTO_FUNCTION_NAME: 'kek' }
+resp = test_request(header, body)
+print "Sync: ", resp['header'][IPROTO_SYNC]
+print "Retcode: ", resp['body'][IPROTO_DATA]
+
+c.close()
+
+admin("box.schema.user.revoke('guest', 'read,write,execute', 'universe')")
+
 admin("space:drop()")